From 2b8377c41f980fb94192d38be212b2edc4526404 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Wed, 21 Oct 2026 12:30:42 +0200 Subject: [PATCH 01/37] Add oxlint config and lint pipeline --- .github/workflows/oxlint.yml | 70 + .github/workflows/preDeploy.yml | 6 + .gitignore | 4 + .npmrc | 5 + config/eslint/eslint.config.mjs | 3 + config/oxlint/eslintDirectives.mjs | 126 + config/oxlint/noDeprecatedSelfClosingJsx.mjs | 162 + config/oxlint/oxlint.seatbelt.tsv | 2595 +++++++++++++++++ config/oxlint/plugins/core-rules.mjs | 29 + config/oxlint/plugins/expensify-rules.mjs | 40 + config/oxlint/plugins/hosted-rules.mjs | 97 + config/oxlint/plugins/rc-rules.mjs | 41 + .../oxlint/preferLocaleCompareFromContext.mjs | 53 + config/oxlint/reactCompilerGate.mjs | 62 + config/oxlint/reactCompilerRust.mjs | 236 ++ config/oxlint/ruleNames.d.mts | 7 + config/oxlint/ruleNames.mjs | 98 + config/reactCompiler/suppressedRules.d.mts | 4 + config/reactCompiler/suppressedRules.mjs | 8 + cspell.json | 10 + knip.json | 7 +- oxlint.config.mts | 1327 +++++++++ package-lock.json | 684 ++++- package.json | 6 +- scripts/lint/formatters/JSONFormatter.ts | 26 + scripts/lint/index.ts | 74 +- scripts/lint/oxlint/OxlintLinter.ts | 498 ++++ .../lint/processors/ReactCompilerFilter.ts | 4 +- scripts/lint/processors/Seatbelt.ts | 27 +- tests/tooling/lintPipeline.test.ts | 465 ++- tsconfig.node.json | 2 +- 31 files changed, 6664 insertions(+), 112 deletions(-) create mode 100644 .github/workflows/oxlint.yml create mode 100644 config/oxlint/eslintDirectives.mjs create mode 100644 config/oxlint/noDeprecatedSelfClosingJsx.mjs create mode 100644 config/oxlint/oxlint.seatbelt.tsv create mode 100644 config/oxlint/plugins/core-rules.mjs create mode 100644 config/oxlint/plugins/expensify-rules.mjs create mode 100644 config/oxlint/plugins/hosted-rules.mjs create mode 100644 config/oxlint/plugins/rc-rules.mjs create mode 100644 config/oxlint/preferLocaleCompareFromContext.mjs create mode 100644 config/oxlint/reactCompilerGate.mjs create mode 100644 config/oxlint/reactCompilerRust.mjs create mode 100644 config/oxlint/ruleNames.d.mts create mode 100644 config/oxlint/ruleNames.mjs create mode 100644 config/reactCompiler/suppressedRules.d.mts create mode 100644 config/reactCompiler/suppressedRules.mjs create mode 100644 oxlint.config.mts create mode 100644 scripts/lint/formatters/JSONFormatter.ts create mode 100644 scripts/lint/oxlint/OxlintLinter.ts diff --git a/.github/workflows/oxlint.yml b/.github/workflows/oxlint.yml new file mode 100644 index 000000000000..b7af5f9e0b88 --- /dev/null +++ b/.github/workflows/oxlint.yml @@ -0,0 +1,70 @@ +name: Oxlint check (non-blocking) + +on: + workflow_call: + pull_request: + types: [opened, synchronize] + branches-ignore: [staging, production] + paths: ['**.js', '**.ts', '**.tsx', '**.json', '**.mjs', '**.cjs', 'oxlint.config.mts', 'config/oxlint/**', 'scripts/lint/**', '.github/workflows/oxlint.yml'] + +concurrency: + group: ${{ github.ref == 'refs/heads/main' && format('{0}-{1}', github.ref, github.sha) || github.ref }}-oxlint + cancel-in-progress: true + +jobs: + oxlint: + name: Oxlint check (non-blocking) + if: ${{ github.event.head_commit.author.name != 'OSBotify' || github.event_name == 'push' }} + runs-on: blacksmith-8vcpu-ubuntu-2404 + steps: + - name: Checkout + uses: useblacksmith/checkout@0647fdbab2614a5eb86d2971070e325060d91056 # v1.7.0 + with: + token: ${{ github.event_name == 'push' && secrets.OS_BOTIFY_COMMIT_TOKEN || github.token }} + + - name: Setup Node + uses: ./.github/actions/composite/setupNode + + - name: Lint JavaScript and TypeScript with Oxlint + id: oxlint + continue-on-error: true + # oxlint runs JS plugins on one thread (oxc#26621), so OxlintLinter fans them across 4 + # single-threaded shards and runs the type-aware rules in one multi-threaded process: 8 vCPU. + env: + OXLINT_SHARDS: 4 + run: npm run lint -- --linter=oxlint + + - name: Report the Oxlint result without failing the job + run: | + if [[ "${{ steps.oxlint.outcome }}" == "success" ]]; then + echo "::notice::Oxlint found no errors above its baseline." + else + echo "::warning::Oxlint reported errors above its baseline. Non-blocking during the migration; see the step log." + fi + + - name: Check for tightened oxlint-seatbelt baseline + id: seatbelt_diff + if: github.event_name == 'push' + run: | + if git diff --quiet config/oxlint/oxlint.seatbelt.tsv; then + echo "tightened=false" >> "$GITHUB_OUTPUT" + echo "::notice::oxlint.seatbelt.tsv unchanged; skipping auto-commit." + else + echo "tightened=true" >> "$GITHUB_OUTPUT" + fi + + - name: Setup git for OSBotify + if: steps.seatbelt_diff.outputs.tightened == 'true' + uses: Expensify/GitHub-Actions/setupGitForOSBotify@main + with: + OP_VAULT: ${{ vars.OP_VAULT }} + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + SETUP_AS_APP: false + + - name: Auto-commit tightened oxlint-seatbelt baseline + if: steps.seatbelt_diff.outputs.tightened == 'true' + continue-on-error: true + run: | + git add config/oxlint/oxlint.seatbelt.tsv + git commit -m "Auto-tighten oxlint-seatbelt baseline" + git push origin main diff --git a/.github/workflows/preDeploy.yml b/.github/workflows/preDeploy.yml index 0c09e86b3098..c6f5a41a72d3 100644 --- a/.github/workflows/preDeploy.yml +++ b/.github/workflows/preDeploy.yml @@ -14,6 +14,12 @@ jobs: uses: ./.github/workflows/lint.yml secrets: inherit + # Deliberately absent from confirmPassingBuild's `needs`: the Oxlint run is a shadow gate, here + # only so the post-merge job can auto-tighten its own baseline. + oxlint: + uses: ./.github/workflows/oxlint.yml + secrets: inherit + oxfmt: uses: ./.github/workflows/oxfmt.yml diff --git a/.gitignore b/.gitignore index 8c6d0691fdb9..d82481e269f0 100644 --- a/.gitignore +++ b/.gitignore @@ -185,3 +185,7 @@ server/victory-chart-renderer/.dev/ # Bun lockfiles (VCR uses npm workspaces + package-lock.json; bun is runtime-only) bun.lock + +# Oxlint leg configs derived from oxlint.config.mts for one sharded run (scripts/lint/oxlint/OxlintLinter.ts) +.oxlintrc.js-plugins.*.json +.oxlintrc.type-aware.*.json diff --git a/.npmrc b/.npmrc index 2a14ea859d36..eb8b3f295df6 100644 --- a/.npmrc +++ b/.npmrc @@ -10,3 +10,8 @@ min-release-age-exclude[]=@expensify/react-native-wallet min-release-age-exclude[]=react-fast-pdf min-release-age-exclude[]=react-native-key-command min-release-age-exclude[]=react-native-qrcode-svg +# Omitting a binding scope makes npm silently skip the optional dependency instead of erroring. +min-release-age-exclude[]=oxlint +min-release-age-exclude[]=@oxlint/* +min-release-age-exclude[]=oxc-transform-react +min-release-age-exclude[]=@oxc-transform-react/* diff --git a/config/eslint/eslint.config.mjs b/config/eslint/eslint.config.mjs index 6696ee50f090..50387071594e 100644 --- a/config/eslint/eslint.config.mjs +++ b/config/eslint/eslint.config.mjs @@ -824,6 +824,9 @@ const config = defineConfig([ globalIgnores([ '!**/.storybook', '!**/.github', + // Oxlint's own config. Its rule names are quoted object keys, which naming-convention flags by the + // hundred; `**/*.config.mjs` below is ignored for the same kind of reason. + 'oxlint.config.mts', '.github/actions/**/index.js', '**/*.config.js', '**/*.config.mjs', diff --git a/config/oxlint/eslintDirectives.mjs b/config/oxlint/eslintDirectives.mjs new file mode 100644 index 000000000000..9cd448334fb2 --- /dev/null +++ b/config/oxlint/eslintDirectives.mjs @@ -0,0 +1,126 @@ +// A rule hosted through oxlint's `jsPlugins` cannot take a native plugin's prefix (`react-hooks`, +// `@typescript-eslint`, `react`, `import` are reserved), so an existing `eslint-disable-next-line +// @typescript-eslint/naming-convention` does not suppress `hosted/naming-convention`. This makes a +// hosted rule answer to the id ESLint uses, by dropping the reports ESLint would have dropped. + +const JUSTIFICATION = /\s-{2,}\s/; +const KINDS = ['eslint-disable-next-line', 'eslint-disable-line', 'eslint-disable', 'eslint-enable']; +const ALL_RULES = ' all'; + +const cache = new Map(); + +function parseComment(comment) { + const text = String(comment.value ?? '').trim(); + const kind = KINDS.find((candidate) => text === candidate || (text.startsWith(candidate) && /^\s/.test(text.slice(candidate.length)))); + if (!kind) { + return null; + } + const body = text.slice(kind.length).split(JUSTIFICATION)[0]; + const ruleIds = body + .split(',') + .map((id) => id.trim()) + .filter(Boolean); + return {kind, ruleIds: ruleIds.length ? ruleIds : [ALL_RULES]}; +} + +function parseDirectives(comments) { + const lines = new Map(); + const blocks = []; + + for (const comment of comments) { + const parsed = parseComment(comment); + const start = comment.loc?.start?.line; + const end = comment.loc?.end?.line; + if (!parsed || start == null || end == null) { + continue; + } + for (const ruleId of parsed.ruleIds) { + if (parsed.kind === 'eslint-disable-next-line' || parsed.kind === 'eslint-disable-line') { + // ESLint rejects only a multi-line `-line` directive; a multi-line `-next-line` is + // applied to the line after the comment closes, which is why the target is `end`. + if (parsed.kind === 'eslint-disable-line' && start !== end) { + continue; + } + if (!lines.has(ruleId)) { + lines.set(ruleId, new Set()); + } + lines.get(ruleId).add(parsed.kind === 'eslint-disable-line' ? start : end + 1); + } else { + blocks.push({line: start, ruleId, disable: parsed.kind === 'eslint-disable'}); + } + } + } + + return {lines, blocks}; +} + +function directivesFor(filename, comments) { + const key = filename ?? ''; + if (!cache.has(key)) { + cache.set(key, parseDirectives(comments)); + } + return cache.get(key); +} + +// Mirrors ESLint: the last block directive at or before the line that names the rule (or no rule) +// decides. A bare `eslint-disable` followed by `eslint-enable foo` therefore re-enables `foo` alone, +// and a bare `eslint-enable` re-enables every rule. +function isSuppressed(directives, eslintRuleId, line) { + if (line == null) { + return false; + } + if (directives.lines.get(eslintRuleId)?.has(line) || directives.lines.get(ALL_RULES)?.has(line)) { + return true; + } + let disabled = false; + for (const block of directives.blocks) { + if (block.line > line || (block.ruleId !== ALL_RULES && block.ruleId !== eslintRuleId)) { + continue; + } + // A report on the `eslint-enable` line itself stays suppressed, as with the previous inclusive range. + if (block.disable) { + disabled = true; + } else if (block.line < line) { + disabled = false; + } + } + return disabled; +} + +function reportLine(args) { + const first = args[0]; + const candidates = [first?.loc?.start?.line, first?.loc?.line, first?.node?.loc?.start?.line, first?.node?.loc?.line]; + return candidates.find((line) => typeof line === 'number') ?? null; +} + +function withEslintDirectiveIds(rule, eslintRuleId) { + return { + ...rule, + create(context) { + const filteredContext = Object.create(context, { + report: { + // Forwarded as-is rather than as one descriptor: ESLint also accepts the legacy + // `report(node, message)` form, and dropping the extra arguments would change the message. + value(...args) { + const line = reportLine(args); + if (line != null) { + const sourceCode = context.sourceCode ?? context.getSourceCode(); + const filename = context.filename ?? context.getFilename(); + if (isSuppressed(directivesFor(filename, sourceCode.getAllComments()), eslintRuleId, line)) { + return; + } + } + return context.report(...args); + }, + }, + }); + return rule.create(filteredContext); + }, + }; +} + +function withEslintDirectiveIdsFor(rules, toEslintId) { + return Object.fromEntries(Object.entries(rules).map(([name, rule]) => [name, withEslintDirectiveIds(rule, toEslintId(name))])); +} + +export {withEslintDirectiveIds, withEslintDirectiveIdsFor}; diff --git a/config/oxlint/noDeprecatedSelfClosingJsx.mjs b/config/oxlint/noDeprecatedSelfClosingJsx.mjs new file mode 100644 index 000000000000..74723fcbb1be --- /dev/null +++ b/config/oxlint/noDeprecatedSelfClosingJsx.mjs @@ -0,0 +1,162 @@ +// tsgolint's no-deprecated skips the signature check on self-closing JSX (``, not ``), so a +// component deprecated only through its signature, like `MenuItem` wrapped in `Object.assign`, goes +// unreported. jsPlugins get no types, so the components are listed in the options. Delete this rule once +// tsgolint reports these itself: the findings will double. + +import fs from 'node:fs'; +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; + +const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..'); + +// tsconfig.json allows comments and trailing commas. +function readJsonc(file) { + const text = fs + .readFileSync(file, 'utf8') + .replaceAll(/("(?:\\.|[^"\\])*")|\/\/[^\n]*|\/\*[\s\S]*?\*\//g, (match, string) => string ?? '') + .replaceAll(/,(\s*[}\]])/g, '$1'); + return JSON.parse(text); +} + +let aliasPrefixes; + +function getAliasPrefixes() { + if (!aliasPrefixes) { + const paths = readJsonc(path.join(repoRoot, 'tsconfig.json')).compilerOptions?.paths ?? {}; + aliasPrefixes = Object.entries(paths) + .filter(([alias, targets]) => alias.endsWith('/*') && targets.at(0)?.endsWith('/*')) + .map(([alias, targets]) => [alias.slice(0, -1), path.resolve(repoRoot, targets.at(0).slice(0, -1))]); + } + return aliasPrefixes; +} + +function resolveModule(source, filename) { + let resolved = null; + if (source.startsWith('.')) { + resolved = path.resolve(path.dirname(filename), source); + } else { + const match = getAliasPrefixes().find(([prefix]) => source.startsWith(prefix)); + resolved = match ? path.join(match[1], source.slice(match[0].length)) : null; + } + return resolved?.replace(/\.(tsx?|jsx?|mjs)$/, '').replace(/\/index$/, '') ?? null; +} + +const meta = { + type: 'problem', + docs: { + description: 'Report self-closing JSX of a component deprecated through its call signature, which tsgolint misses', + }, + schema: [ + { + type: 'object', + properties: { + components: { + type: 'array', + items: { + type: 'object', + properties: { + module: {type: 'string'}, + importName: {type: 'string'}, + reason: {type: 'string'}, + }, + required: ['module', 'importName'], + additionalProperties: false, + }, + }, + }, + additionalProperties: false, + }, + ], +}; + +function importedName(specifier) { + if (specifier.type === 'ImportDefaultSpecifier') { + return 'default'; + } + if (specifier.type === 'ImportSpecifier') { + return specifier.imported.type === 'Identifier' ? specifier.imported.name : specifier.imported.value; + } + return null; +} + +function initializerIdentifiers(node) { + if (!node) { + return []; + } + switch (node.type) { + case 'Identifier': + return [node.name]; + case 'ConditionalExpression': + return [...initializerIdentifiers(node.consequent), ...initializerIdentifiers(node.alternate)]; + case 'LogicalExpression': + return [...initializerIdentifiers(node.left), ...initializerIdentifiers(node.right)]; + case 'TSAsExpression': + case 'TSSatisfiesExpression': + case 'TSNonNullExpression': + return initializerIdentifiers(node.expression); + default: + return []; + } +} + +function create(context) { + const components = context.options[0]?.components ?? []; + if (components.length === 0) { + return {}; + } + + const deprecatedBindings = new Map(); + const aliases = []; + const selfClosingTags = []; + + return { + ImportDeclaration(node) { + const resolved = resolveModule(String(node.source.value), context.filename ?? context.getFilename()); + for (const component of components) { + if (resolved === null || resolved !== path.resolve(repoRoot, component.module)) { + continue; + } + for (const specifier of node.specifiers) { + if (importedName(specifier) === component.importName) { + deprecatedBindings.set(specifier.local.name, component); + } + } + } + }, + VariableDeclarator(node) { + if (node.id.type !== 'Identifier') { + return; + } + aliases.push({name: node.id.name, targets: initializerIdentifiers(node.init)}); + }, + JSXOpeningElement(node) { + if (!node.selfClosing || node.name.type !== 'JSXIdentifier') { + return; + } + selfClosingTags.push(node.name); + }, + 'Program:exit': function () { + if (deprecatedBindings.size === 0) { + return; + } + for (const alias of aliases) { + const target = alias.targets.find((name) => deprecatedBindings.has(name)); + if (target && !deprecatedBindings.has(alias.name)) { + deprecatedBindings.set(alias.name, deprecatedBindings.get(target)); + } + } + for (const name of selfClosingTags) { + const component = deprecatedBindings.get(name.name); + if (!component) { + continue; + } + context.report({ + node: name, + message: component.reason ? `\`${name.name}\` is deprecated. ${component.reason}` : `\`${name.name}\` is deprecated.`, + }); + } + }, + }; +} + +export {meta, create}; diff --git a/config/oxlint/oxlint.seatbelt.tsv b/config/oxlint/oxlint.seatbelt.tsv new file mode 100644 index 000000000000..4831d1a255ae --- /dev/null +++ b/config/oxlint/oxlint.seatbelt.tsv @@ -0,0 +1,2595 @@ +# eslint-seatbelt temporarily allowed errors +# docs: https://github.com/justjake/eslint-seatbelt#readme + +"../../__mocks__/@pusher/pusher-websocket-react-native/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../__mocks__/@react-native-camera-roll/camera-roll.ts" "@typescript-eslint/no-deprecated/CameraRoll.save" 1 +"../../__mocks__/@react-native-community/netinfo.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../__mocks__/@react-navigation/native/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../__mocks__/@react-navigation/native/index.ts" "import/export" 1 +"../../__mocks__/expo-video.tsx" "react-hooks/refs" 2 +"../../__mocks__/openai.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../__mocks__/react-freeze.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../__mocks__/react-native-onyx.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../__mocks__/react-native-safe-area-context.tsx" "react-hooks/refs" 2 +"../../__mocks__/react-native.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../.github/actions/javascript/failureNotifier/failureNotifier.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../.github/actions/javascript/formatCodeCovComment/formatCodeCovComment.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../.github/actions/javascript/getAndroidRolloutPercentage/getAndroidRolloutPercentage.ts" "rulesdir/no-default-id-values" 1 +"../../.github/actions/javascript/getPreviousVersion/getPreviousVersion.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../.github/actions/javascript/getPullRequestDetails/getPullRequestDetails.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../.github/actions/javascript/getPullRequestIncrementalChanges/getPullRequestIncrementalChanges.ts" "@typescript-eslint/no-unsafe-type-assertion" 5 +"../../.github/actions/javascript/markPullRequestsAsDeployed/markPullRequestsAsDeployed.ts" "no-restricted-imports" 1 +"../../.github/actions/javascript/postOrReplaceComment/postOrReplaceComment.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../.github/actions/javascript/proposalPoliceComment/proposalPoliceComment.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../.github/actions/javascript/reviewerChecklist/reviewerChecklist.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../.github/actions/javascript/validateReassureOutput/validateReassureOutput.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../.github/libs/DeployChecklistUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../.github/libs/GithubUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 5 +"../../.github/libs/sanitizeJSONStringValues.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../.github/scripts/createDocsRoutes.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../.storybook/mocks/react-navigation-native.ts" "import/export" 1 +"../../.storybook/mocks/react-navigation-stack.ts" "import/export" 1 +"../../assets/emojis/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../jest/setup.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../jest/setup.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../jest/setupAfterEnv.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../jest/setupAfterEnv.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../modules/ExpensifyNitroUtils/src/index.ts" "no-restricted-syntax" 1 +"../../scripts/aggregateGitHubDataFromUpwork.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../scripts/bumpVersion.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../scripts/combine-web-sourcemaps.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../scripts/compareKnipReports.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../scripts/compareKnipReports.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../scripts/createOrUpdateDeployChecklist.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../scripts/generateTranslations.ts" "@typescript-eslint/no-unsafe-type-assertion" 8 +"../../scripts/react-compiler-compliance-check.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../scripts/react-compiler-compliance-check.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../scripts/sentry/release-adoption-older-than.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../scripts/utils/Git.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../scripts/utils/OpenAIUtils.ts" "@typescript-eslint/no-deprecated/prompt_cache_retention" 1 +"../../scripts/utils/OpenAIUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../scripts/utils/Translator/TranslationPromptUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../scripts/utils/TSCompilerUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../server/stubs/react-native-reanimated.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../server/stubs/react-native/index.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../server/victory-chart-renderer/src/initSkiaForCli.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../server/victory-chart-renderer/tests/log.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/components/ActionSheetAwareScrollView/useActionSheetAwareScrollViewRef.ts" "react-hooks/immutability" 1 +"../../src/components/ActionSheetAwareScrollView/useActionSheetKeyboardSpacing.ts" "@typescript-eslint/no-deprecated/useScrollViewOffset" 1 +"../../src/components/AddPlaidBankAccount.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/AddressForm.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/components/AddressForm.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/components/AddressSearch/CurrentLocationButton.tsx" "no-restricted-syntax" 1 +"../../src/components/AddressSearch/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/AddressSearch/isCurrentTargetInsideContainer.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/AgentRules/AgentRulesList.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/components/AmountTextInput.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx" "import/no-cycle" 1 +"../../src/components/AnchorForAttachmentsOnly/index.tsx" "import/no-cycle" 1 +"../../src/components/AnchorForAttachmentsOnly/index.tsx" "no-restricted-syntax" 1 +"../../src/components/AnchorForCommentsOnly/index.tsx" "no-restricted-syntax" 1 +"../../src/components/AnimatedFlatListWithCellRenderer.tsx" "@typescript-eslint/no-deprecated/Animated.createAnimatedComponent" 1 +"../../src/components/AnimatedFlatListWithCellRenderer.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/components/AnimatedSubmitButton/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/AnimatedSubmitButton/index.tsx" "react-hooks/refs" 2 +"../../src/components/AnimatedSubmitButton/index.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/components/ApprovalWorkflowSection.tsx" "@typescript-eslint/no-deprecated/MenuItem" 2 +"../../src/components/ApproverSelectionList.tsx" "no-restricted-imports" 1 +"../../src/components/AttachmentPicker/index.native.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/components/AttachmentPicker/index.native.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/components/AttachmentPicker/index.native.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/AttachmentPicker/index.native.tsx" "react-hooks/refs" 1 +"../../src/components/AttachmentPicker/index.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/components/AttachmentPicker/index.tsx" "react-hooks/refs" 1 +"../../src/components/AttachmentPreview.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Attachments/AttachmentCarousel/AttachmentCarouselView/index.tsx" "@typescript-eslint/no-deprecated/Gesture.Pan" 2 +"../../src/components/Attachments/AttachmentCarousel/AttachmentCarouselView/index.tsx" "@typescript-eslint/no-deprecated/GestureType" 3 +"../../src/components/Attachments/AttachmentCarousel/AttachmentCarouselView/index.tsx" "@typescript-eslint/no-deprecated/LegacyComposedGesture" 1 +"../../src/components/Attachments/AttachmentCarousel/AttachmentCarouselView/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/components/Attachments/AttachmentCarousel/AttachmentCarouselView/index.tsx" "react-hooks/refs" 3 +"../../src/components/Attachments/AttachmentCarousel/index.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/components/Attachments/AttachmentCarousel/Pager/index.tsx" "@typescript-eslint/no-deprecated/Gesture.Native" 2 +"../../src/components/Attachments/AttachmentCarousel/Pager/index.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/components/Attachments/AttachmentCarousel/Pager/types.ts" "@typescript-eslint/no-deprecated/GestureType" 2 +"../../src/components/Attachments/AttachmentCarousel/useCarouselArrows.ts" "no-restricted-syntax" 1 +"../../src/components/Attachments/AttachmentView/AttachmentViewPdf/index.android.tsx" "@typescript-eslint/no-deprecated/Gesture.Pan" 2 +"../../src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx" "import/no-cycle" 1 +"../../src/components/Attachments/AttachmentView/AttachmentViewVideo/index.tsx" "import/no-cycle" 1 +"../../src/components/Attachments/AttachmentView/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 4 +"../../src/components/Attachments/AttachmentView/index.tsx" "import/no-cycle" 3 +"../../src/components/AutoCompleteSuggestions/AutoCompleteSuggestionsPortal/TransparentOverlay/TransparentOverlay.tsx" "no-restricted-syntax" 1 +"../../src/components/AutoCompleteSuggestions/AutoCompleteSuggestionsPortal/TransparentOverlay/TransparentOverlay.tsx" "react-hooks/refs" 1 +"../../src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/AutoCompleteSuggestions/index.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/components/AutoUpdateTime.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/components/AutoUpdateTime.tsx" "react-hooks/purity" 1 +"../../src/components/AvatarCropModal/AvatarCropView.tsx" "no-restricted-imports" 1 +"../../src/components/AvatarCropModal/ImageCropView.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/AvatarCropModal/Slider.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/AvatarSelector.tsx" "no-restricted-syntax" 2 +"../../src/components/AvatarWithImagePicker.tsx" "react-hooks/set-state-in-effect" 2 +"../../src/components/BaseVacationDelegateSelectionComponent.tsx" "no-restricted-imports" 1 +"../../src/components/BlockingViews/FullPageNotFoundView.tsx" "no-restricted-imports" 1 +"../../src/components/BookTravelButton.tsx" "no-restricted-imports" 1 +"../../src/components/ButtonWithDropdownMenu/index.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/components/CategoryPicker/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Charts/hooks/useChartInteractions.ts" "@typescript-eslint/no-deprecated/Gesture.Hover" 2 +"../../src/components/Charts/hooks/useChartInteractions.ts" "@typescript-eslint/no-deprecated/Gesture.Race" 2 +"../../src/components/Charts/hooks/useChartInteractions.ts" "@typescript-eslint/no-deprecated/Gesture.Tap" 2 +"../../src/components/Charts/hooks/useChartLabelFormats.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Charts/hooks/useChartLabelMeasurements.ts" "rulesdir/no-raw-typography" 1 +"../../src/components/Charts/PieChart/PieChartContent.tsx" "@typescript-eslint/no-deprecated/Gesture.Hover" 2 +"../../src/components/Charts/PieChart/PieChartContent.tsx" "@typescript-eslint/no-deprecated/Gesture.Race" 2 +"../../src/components/Charts/PieChart/PieChartContent.tsx" "@typescript-eslint/no-deprecated/Gesture.Tap" 2 +"../../src/components/Charts/utils/chartFontAssets.native.ts" "@typescript-eslint/no-unsafe-type-assertion" 13 +"../../src/components/Charts/utils/chartFontAssets.ts" "@typescript-eslint/no-unsafe-type-assertion" 13 +"../../src/components/Charts/utils/chartFontsCache.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/components/Charts/utils/chartWebFont.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Charts/utils/getChartSkiaTypeface.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/CheckboxWithLabel.tsx" "no-restricted-syntax" 1 +"../../src/components/ColumnsSettingsList.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/ColumnsSettingsList.tsx" "no-restricted-imports" 1 +"../../src/components/Composer/implementation/index.native.tsx" "react-hooks/preserve-manual-memoization" 1 +"../../src/components/Composer/implementation/index.tsx" "react-hooks/set-state-in-effect" 2 +"../../src/components/ConfirmModal.tsx" "@typescript-eslint/no-deprecated/ConfirmModal" 1 +"../../src/components/ConnectionLayout.tsx" "no-restricted-imports" 1 +"../../src/components/ConnectToMergeFlow/index.ios.tsx" "no-restricted-imports" 1 +"../../src/components/ConnectToMergeFlow/index.native.tsx" "no-restricted-imports" 1 +"../../src/components/ConnectToNetSuiteFlow/index.tsx" "react-hooks/refs" 5 +"../../src/components/ConnectToNetSuiteFlow/index.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/components/ConnectToXeroFlow/index.native.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/components/ConnectToXeroFlow/index.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/components/CountryPicker/CountrySelectorModal.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/CountryPicker/CountrySelectorModal.tsx" "no-restricted-imports" 1 +"../../src/components/CountryPicker/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/CountrySelector.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/components/createOnyxContext.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/components/CurrencyPicker.tsx" "no-restricted-imports" 1 +"../../src/components/CurrencySelector.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/components/CustomUnitDefaultCategorySelector/index.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/components/DatePicker/CalendarPicker/index.tsx" "react-hooks/purity" 2 +"../../src/components/DatePicker/CalendarPicker/MonthPickerModal.tsx" "no-restricted-imports" 1 +"../../src/components/DatePicker/CalendarPicker/MonthPickerModal.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/components/DatePicker/CalendarPicker/YearPickerModal.tsx" "no-restricted-imports" 1 +"../../src/components/DatePicker/CalendarPicker/YearPickerModal.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/components/DatePicker/DatePickerModal.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/components/DatePicker/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/DatePicker/index.tsx" "react-hooks/refs" 1 +"../../src/components/DatePicker/index.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/components/DialogLabelContext.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/DisplayNames/DisplayNamesWithTooltip.tsx" "react-hooks/refs" 6 +"../../src/components/DistanceEReceiptPanel.tsx" "import/no-cycle" 1 +"../../src/components/DistanceRequest/DistanceRequestRenderItem.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/components/DistanceRequest/DistanceRequestRenderItem.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/DotIndicatorMessage.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/components/DotIndicatorMessage.tsx" "import/no-cycle" 1 +"../../src/components/DragAndDrop/NoDropZone/index.tsx" "react-hooks/refs" 1 +"../../src/components/DragAndDrop/Provider/index.tsx" "react-hooks/refs" 4 +"../../src/components/DraggableList/SortableItem.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/components/DraggableList/SortableItem.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/DropZone/DropZoneWrapper.tsx" "react-hooks/refs" 1 +"../../src/components/EditableCell/usePopoverEditState.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/EmbeddedDemo/index.native.tsx" "import/no-named-as-default" 1 +"../../src/components/EmojiPicker/EmojiPicker.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/components/EmojiPicker/EmojiPicker.tsx" "react-hooks/refs" 3 +"../../src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx" "react-hooks/preserve-manual-memoization" 1 +"../../src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx" "react-hooks/refs" 2 +"../../src/components/EmojiPicker/EmojiPickerMenu/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/components/EmojiPicker/EmojiPickerMenu/index.tsx" "react-hooks/preserve-manual-memoization" 1 +"../../src/components/EmojiPicker/EmojiPickerMenu/index.tsx" "react-hooks/refs" 2 +"../../src/components/EmojiPicker/EmojiPickerMenu/useEmojiPickerMenu.ts" "react-hooks/set-state-in-effect" 2 +"../../src/components/EmojiPicker/EmojiSkinToneList.tsx" "react-hooks/set-state-in-effect" 2 +"../../src/components/EmptySelectionListContent.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/EReceiptWithSizeCalculation.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/ErrorMessageRow.tsx" "import/no-cycle" 1 +"../../src/components/FeatureList.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/components/FeatureTraining/FeatureTrainingCarousel.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/components/FeedbackSurvey.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/components/FilePicker/index.native.tsx" "react-hooks/refs" 1 +"../../src/components/FilePicker/index.tsx" "react-hooks/refs" 1 +"../../src/components/FlatList/hooks/useFlatListHandle.ts" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/components/FocusableMenuItem.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/components/FocusTrap/FocusTrapContainerElement/index.web.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/FocusTrap/FocusTrapForScreen/index.web.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Form/FormProvider.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/components/Form/FormProvider.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/components/Form/FormProvider.tsx" "react-hooks/immutability" 2 +"../../src/components/Form/FormProvider.tsx" "react-hooks/refs" 2 +"../../src/components/Form/InputWrapper.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/components/Form/InputWrapper.tsx" "react-hooks/refs" 1 +"../../src/components/FormHelpMessage.tsx" "import/no-cycle" 1 +"../../src/components/HeaderPageLayout.tsx" "no-restricted-imports" 2 +"../../src/components/HeaderWithBackButton/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/components/HighlightableMenuItem.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/components/HighlightableMenuItemWithTopDescription.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/components/Hoverable/ActiveHoverable.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Hoverable/ActiveHoverable.tsx" "react-hooks/refs" 3 +"../../src/components/Hoverable/ActiveHoverable.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/components/Hoverable/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Hoverable/index.tsx" "react-hooks/refs" 1 +"../../src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx" "@typescript-eslint/no-unsafe-type-assertion" 5 +"../../src/components/HTMLEngineProvider/HTMLRenderers/AccountManagerLinkRenderer.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.tsx" "import/no-cycle" 1 +"../../src/components/HTMLEngineProvider/HTMLRenderers/ConciergeLinkRenderer.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.tsx" "@typescript-eslint/no-deprecated/fontSize" 3 +"../../src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/components/HTMLEngineProvider/HTMLRenderers/HTMLTableScroll/index.native.tsx" "@typescript-eslint/no-deprecated/Gesture.Pan" 2 +"../../src/components/HTMLEngineProvider/HTMLRenderers/index.ts" "import/no-cycle" 2 +"../../src/components/HTMLEngineProvider/HTMLRenderers/MentionHereRenderer.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/HTMLEngineProvider/HTMLRenderers/RBRRenderer.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/HTMLEngineProvider/HTMLRenderers/ShortMentionRenderer.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/HTMLEngineProvider/HTMLRenderers/TransactionHistoryLinkRenderer.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/components/VictoryChartPie.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/getSkiaLineMetrics.ts" "rulesdir/no-raw-typography" 1 +"../../src/components/HTMLEngineProvider/HTMLRenderers/VideoRenderer.tsx" "import/no-cycle" 1 +"../../src/components/HTMLEngineProvider/index.tsx" "no-restricted-syntax" 1 +"../../src/components/Icon/BankIcons/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Icon/BankIconsUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 45 +"../../src/components/Icon/ExpensifyIconLoader.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Icon/IllustrationLoader.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Image/BaseImage.native.tsx" "@typescript-eslint/no-unsafe-type-assertion" 4 +"../../src/components/Image/BaseImage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 4 +"../../src/components/Image/index.tsx" "react-hooks/preserve-manual-memoization" 2 +"../../src/components/Image/index.tsx" "react-hooks/purity" 1 +"../../src/components/Image/index.tsx" "react-hooks/refs" 1 +"../../src/components/ImageSVG/index.android.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/ImageSVG/index.ios.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/ImageSVG/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/ImportOnyxState/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/ImportSpreadsheet.tsx" "@typescript-eslint/no-unsafe-type-assertion" 4 +"../../src/components/ImportSpreadsheet.tsx" "no-restricted-imports" 1 +"../../src/components/ImportSpreadsheet.tsx" "react-hooks/refs" 1 +"../../src/components/InitialURLContextProvider.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/InteractiveStepWrapper.tsx" "no-restricted-imports" 1 +"../../src/components/KYCWall/BaseKYCWall.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/components/KYCWall/BaseKYCWall.tsx" "react-hooks/preserve-manual-memoization" 1 +"../../src/components/KYCWall/BaseKYCWall.tsx" "react-hooks/refs" 2 +"../../src/components/LHNOptionsList/LHNEmptyState.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/LHNOptionsList/OptionRowLHN/OptionRow/Pressable.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/LocaleContextProvider.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/LocaleContextProvider.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/components/Lottie/index.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/components/MapView/MapView.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/components/MapView/MapView.web.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/components/MapView/ToggleDistanceUnitButton/index.android.tsx" "no-restricted-syntax" 1 +"../../src/components/MenuItem/index.ts" "import/no-cycle" 4 +"../../src/components/MenuItem/leaves/content/MenuItemFieldValueHTML.tsx" "import/no-cycle" 1 +"../../src/components/MenuItem/leaves/helpText/BaseMenuItemHelpText.tsx" "import/no-cycle" 1 +"../../src/components/MenuItem/leaves/helpText/MenuItemHelpText.tsx" "import/no-cycle" 1 +"../../src/components/MenuItem/leaves/helpText/MenuItemHelpTextHTML.tsx" "import/no-cycle" 2 +"../../src/components/MenuItem/MenuItem.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/components/MenuItem/MenuItem.tsx" "import/no-cycle" 2 +"../../src/components/MenuItemList.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/components/MessagesRow.tsx" "import/no-cycle" 1 +"../../src/components/Modal/BaseModal.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/components/Modal/Global/ConfirmModalWrapper.tsx" "@typescript-eslint/no-deprecated/ConfirmModal" 1 +"../../src/components/Modal/Global/ModalContext.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/components/Modal/ReanimatedModal/Container/GestureHandler.tsx" "@typescript-eslint/no-deprecated/Gesture.Pan" 2 +"../../src/components/Modal/ReanimatedModal/Container/GestureHandler.tsx" "@typescript-eslint/no-deprecated/GestureType" 1 +"../../src/components/Modal/ReanimatedModal/Container/index.web.tsx" "react-hooks/refs" 1 +"../../src/components/MoneyReportHeader.tsx" "no-restricted-imports" 1 +"../../src/components/MoneyReportHeaderActions/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/components/MoneyReportHeaderActions/MoneyReportHeaderSelectionDropdown.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/components/MoneyRequestAmountInput.tsx" "react-hooks/immutability" 2 +"../../src/components/MoneyRequestConfirmationList/ConfirmationFooterContent.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/MoneyRequestConfirmationList/hooks/useDistanceRequestState.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/MoneyRequestConfirmationList/hooks/useTransactionReportForConfirmation.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/MoneyRequestConfirmationList/sections/AmountField.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/components/MoneyRequestConfirmationList/sections/AmountField.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/MoneyRequestConfirmationList/sections/AttendeeField.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/MoneyRequestConfirmationList/sections/CategoryField.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/components/MoneyRequestConfirmationList/sections/DateField.tsx" "react-hooks/purity" 2 +"../../src/components/MoneyRequestConfirmationList/sections/RateField.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/components/MoneyRequestConfirmationList/sections/selectors.ts" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/components/MoneyRequestConfirmationList/sections/TaxFields.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/MoneyRequestConfirmationListFooter/ConfirmationReceiptThumbnail.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/MoneyRequestConfirmationListFooter/hooks/useReceiptThumbnailSource.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/MoneyRequestConfirmationListFooter/sections/DistanceMapSection.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/MoneyRequestConfirmationListFooter/sections/ReceiptSection.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/MoneyRequestHeader.tsx" "no-restricted-imports" 1 +"../../src/components/MoneyRequestReportView/MoneyRequestReportTransactionsNavigation.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/components/MoneyRequestReportView/MoneyRequestViewReportFields.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/MultifactorAuthentication/components/Modals/createCancelConfirmModal.tsx" "@typescript-eslint/no-deprecated/ConfirmModal" 1 +"../../src/components/MultifactorAuthentication/components/OutcomeScreen/OutcomeScreenBase.tsx" "no-restricted-imports" 1 +"../../src/components/MultifactorAuthentication/config/scenarios/ChangePIN.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/MultifactorAuthentication/config/scenarios/DefaultUserInterface.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/MultifactorAuthentication/Context/stateReducer.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/MultifactorAuthentication/Context/usePromptContent.ts" "react-hooks/refs" 6 +"../../src/components/MultifactorAuthentication/ValidateCodeResendButton.tsx" "no-restricted-syntax" 1 +"../../src/components/MultiGestureCanvas/index.tsx" "@typescript-eslint/no-deprecated/Gesture.Pan" 2 +"../../src/components/MultiGestureCanvas/index.tsx" "@typescript-eslint/no-deprecated/Gesture.Race" 2 +"../../src/components/MultiGestureCanvas/index.tsx" "@typescript-eslint/no-deprecated/Gesture.Simultaneous" 2 +"../../src/components/MultiGestureCanvas/index.tsx" "@typescript-eslint/no-deprecated/GestureRef" 1 +"../../src/components/MultiGestureCanvas/index.tsx" "@typescript-eslint/no-deprecated/GestureType" 2 +"../../src/components/MultiGestureCanvas/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/MultiGestureCanvas/index.tsx" "no-restricted-syntax" 1 +"../../src/components/MultiGestureCanvas/index.tsx" "react-hooks/preserve-manual-memoization" 1 +"../../src/components/MultiGestureCanvas/index.tsx" "react-hooks/refs" 2 +"../../src/components/MultiGestureCanvas/usePanGesture.ts" "@typescript-eslint/no-deprecated/Gesture.Pan" 2 +"../../src/components/MultiGestureCanvas/usePanGesture.ts" "@typescript-eslint/no-deprecated/LegacyPanGesture" 1 +"../../src/components/MultiGestureCanvas/usePanGesture.ts" "no-restricted-syntax" 1 +"../../src/components/MultiGestureCanvas/usePinchGesture.ts" "@typescript-eslint/no-deprecated/Gesture.Pinch" 2 +"../../src/components/MultiGestureCanvas/usePinchGesture.ts" "@typescript-eslint/no-deprecated/LegacyPinchGesture" 1 +"../../src/components/MultiGestureCanvas/usePinchGesture.ts" "react-hooks/set-state-in-effect" 1 +"../../src/components/MultiGestureCanvas/useTapGestures.ts" "@typescript-eslint/no-deprecated/Gesture.Tap" 4 +"../../src/components/MultiGestureCanvas/useTapGestures.ts" "@typescript-eslint/no-deprecated/LegacyTapGesture" 2 +"../../src/components/MultiGestureCanvas/useTapGestures.ts" "no-restricted-syntax" 1 +"../../src/components/Navigation/QuickCreationActionsBar/index.tsx" "no-restricted-imports" 1 +"../../src/components/NumberWithSymbolForm.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/NumberWithSymbolForm.tsx" "react-hooks/set-state-in-effect" 2 +"../../src/components/OfflineWithFeedback.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/components/OfflineWithFeedback.tsx" "import/no-cycle" 1 +"../../src/components/Onfido/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/OptionRow.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/components/OptionRow.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/components/ParentNavigationSubtitle.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/ParticipantPicker/BaseParticipantPicker.tsx" "no-restricted-imports" 1 +"../../src/components/PDFView/index.native.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/PDFView/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/PDFView/index.tsx" "import/no-cycle" 1 +"../../src/components/PDFView/index.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/components/PDFView/PDFPasswordForm.tsx" "import/no-cycle" 1 +"../../src/components/Picker/BasePicker.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/components/Picker/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/PopoverMenu/index.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/components/PopoverMenu/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/PopoverMenu/index.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/components/PopoverMenu/index.tsx" "unicorn/prefer-at" 1 +"../../src/components/PopoverMenu/v2/index.tsx" "import/no-cycle" 2 +"../../src/components/PopoverMenu/v2/rows/index.ts" "import/no-cycle" 3 +"../../src/components/PopoverMenu/v2/rows/Item.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/components/PopoverMenu/v2/rows/Item.tsx" "import/no-cycle" 2 +"../../src/components/PopoverMenu/v2/rows/Label.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/components/PopoverMenu/v2/rows/Label.tsx" "import/no-cycle" 1 +"../../src/components/PopoverMenu/v2/rows/RadioItem.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/components/PopoverMenu/v2/rows/RadioItem.tsx" "import/no-cycle" 2 +"../../src/components/PopoverMenu/v2/sub/index.ts" "import/no-cycle" 2 +"../../src/components/PopoverMenu/v2/sub/SubBackButton.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/components/PopoverMenu/v2/sub/SubBackButton.tsx" "import/no-cycle" 1 +"../../src/components/PopoverMenu/v2/sub/SubTrigger.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/components/PopoverMenu/v2/sub/SubTrigger.tsx" "import/no-cycle" 1 +"../../src/components/PopoverProvider/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Pressable/GenericPressable/implementation/BaseGenericPressable.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../src/components/Pressable/GenericPressable/implementation/BaseGenericPressable.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/components/Pressable/GenericPressable/implementation/BaseGenericPressable.tsx" "react-hooks/refs" 1 +"../../src/components/Pressable/GenericPressable/implementation/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/components/Pressable/PressableWithDelayToggle.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Pressable/PressResponder/useResponderRef.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/components/PressableWithSecondaryInteraction/index.native.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/components/PressableWithSecondaryInteraction/index.native.tsx" "no-restricted-syntax" 1 +"../../src/components/PressableWithSecondaryInteraction/index.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/components/PressableWithSecondaryInteraction/index.tsx" "no-restricted-syntax" 1 +"../../src/components/PromotedActionsBar.tsx" "no-restricted-imports" 1 +"../../src/components/PushRowWithModal/index.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/components/PushRowWithModal/PushRowModal.tsx" "no-restricted-imports" 1 +"../../src/components/RadioButtonWithLabel.tsx" "no-restricted-syntax" 1 +"../../src/components/Reactions/AddReactionBubble.tsx" "react-hooks/refs" 1 +"../../src/components/Reactions/MiniQuickEmojiReactions.tsx" "react-hooks/refs" 1 +"../../src/components/ReceiptAlternativeMethods.tsx" "import/no-cycle" 1 +"../../src/components/ReceiptCropView/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/ReceiptEmptyState.tsx" "import/no-cycle" 1 +"../../src/components/ReceiptHoverZoom/useReceiptHoverZoom.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/ReceiptImage/index.tsx" "import/no-cycle" 1 +"../../src/components/ReceiptImage/index.tsx" "react-hooks/purity" 1 +"../../src/components/ReimbursementAccountLoadingIndicator.tsx" "no-restricted-imports" 1 +"../../src/components/RenderHTML.tsx" "import/no-cycle" 1 +"../../src/components/ReportActionAvatars/index.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/components/ReportActionAvatars/useReportPreviewSenderID.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/components/ReportActionItem/ExportWithDropdownMenu.tsx" "react-hooks/preserve-manual-memoization" 3 +"../../src/components/ReportActionItem/MoneyReportView.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/ReportActionItem/MoneyRequestReceiptView.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/components/ReportActionItem/MoneyRequestReceiptView.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/components/ReportActionItem/MoneyRequestView.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 5 +"../../src/components/ReportActionItem/ReportActionItemImage.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../src/components/ReportActionItem/SupportTicketView.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/components/ReportActionItem/TaskPreview.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/ReportActionItem/TaskView.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/components/ReportActionItem/TaskView.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 2 +"../../src/components/ReportActionItem/TaskView.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx" "@typescript-eslint/no-deprecated/fontSize" 2 +"../../src/components/ReportActionItem/TripDetailsView.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/components/ReportActionItem/TripRoomPreview.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/components/RNMarkdownTextInput.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/RNMaskedTextInput.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/RoomHeaderAvatars.tsx" "no-restricted-syntax" 2 +"../../src/components/Rule/RuleBooleanBase.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Rule/RuleBooleanBase.tsx" "no-restricted-imports" 1 +"../../src/components/Rule/RuleSelectionBase.tsx" "no-restricted-imports" 1 +"../../src/components/Rule/RuleTextBase.tsx" "no-restricted-imports" 1 +"../../src/components/Rule/TextBase.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/components/SAMLLoadingIndicator.tsx" "@typescript-eslint/no-deprecated/fontSize" 1 +"../../src/components/ScaledDistanceEReceipt.tsx" "import/no-cycle" 1 +"../../src/components/ScreenWrapper/ScreenWrapperContainer.tsx" "react-hooks/refs" 2 +"../../src/components/Search/chartGroupByConfig.ts" "@typescript-eslint/no-unsafe-type-assertion" 20 +"../../src/components/Search/FilterComponents/AdvancedFilters/DateFilterContent.tsx" "no-restricted-imports" 1 +"../../src/components/Search/FilterComponents/DateFilterBase.tsx" "no-restricted-imports" 1 +"../../src/components/Search/FilterComponents/DateFilterBase.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/components/Search/FilterComponents/DatePresetFilterBase.tsx" "@typescript-eslint/no-deprecated/MenuItem" 2 +"../../src/components/Search/FilterComponents/DatePresetFilterBase.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/components/Search/FilterComponents/RangeDatePicker.tsx" "react-hooks/purity" 2 +"../../src/components/Search/FilterComponents/ReportField/index.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/components/Search/FilterComponents/ReportField/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Search/FilterComponents/ReportField/index.tsx" "no-restricted-imports" 1 +"../../src/components/Search/FilterComponents/SingleSelect.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Search/FilterComponents/TypeSelector.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Search/FilterDropdowns/AmountPopup.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/components/Search/FilterDropdowns/BasePopup.tsx" "no-restricted-imports" 1 +"../../src/components/Search/FilterDropdowns/DateSelectPopup/SelectedDateModifierHeader.tsx" "no-restricted-imports" 1 +"../../src/components/Search/FilterDropdowns/SearchFooterPopup.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 3 +"../../src/components/Search/FilterDropdowns/SortByPopup.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Search/hooks/useOptimisticSearchTracking.ts" "@typescript-eslint/no-unsafe-type-assertion" 4 +"../../src/components/Search/hooks/useSearchSnapshot.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/components/Search/hooks/useStableOptimisticSortedData.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Search/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 4 +"../../src/components/Search/index.tsx" "react-hooks/preserve-manual-memoization" 5 +"../../src/components/Search/index.tsx" "react-hooks/refs" 5 +"../../src/components/Search/index.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/components/Search/SearchAutocompleteList.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../src/components/Search/SearchBulkActionsButton.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/components/Search/SearchChartView.tsx" "react-hooks/purity" 1 +"../../src/components/Search/SearchContextDefinitions.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Search/SearchList/ListItem/BaseListItemHeader.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/components/Search/SearchList/ListItem/BaseListItemHeader.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Search/SearchList/ListItem/CardListItemHeader.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/components/Search/SearchList/ListItem/CardListItemHeader.tsx" "@typescript-eslint/no-unsafe-type-assertion" 4 +"../../src/components/Search/SearchList/ListItem/ChatListItem.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Search/SearchList/ListItem/ExpenseReportListItem.tsx" "@typescript-eslint/no-unsafe-type-assertion" 4 +"../../src/components/Search/SearchList/ListItem/ExpenseReportListItemRow/ExpenseReportListItemRowWide.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Search/SearchList/ListItem/GroupChildrenContent.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/components/Search/SearchList/ListItem/MemberListItemHeader.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/components/Search/SearchList/ListItem/MemberListItemHeader.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/components/Search/SearchList/ListItem/ReportListItemHeader.tsx" "@typescript-eslint/no-unsafe-type-assertion" 4 +"../../src/components/Search/SearchList/ListItem/SearchMergeReportsListItem.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Search/SearchList/ListItem/TaskListItem.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Search/SearchList/ListItem/TaskListItemRow.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/components/Search/SearchList/ListItem/TransactionGroupListExpanded.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 4 +"../../src/components/Search/SearchList/ListItem/TransactionGroupListExpanded.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Search/SearchList/ListItem/TransactionGroupListItem.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/components/Search/SearchList/ListItem/TransactionGroupListItem.tsx" "@typescript-eslint/no-unsafe-type-assertion" 18 +"../../src/components/Search/SearchList/ListItem/TransactionListItem/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 4 +"../../src/components/Search/SearchList/ListItem/TransactionListItem/TransactionListItemNarrow.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Search/SearchList/ListItem/TransactionListItem/TransactionListItemWide.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Search/SearchList/ListItem/WithdrawalIDListItemHeader.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/components/Search/SearchList/ListItem/WithdrawalIDListItemHeader.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/components/Search/SearchPageHeader/DatePickerFilterPopup.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/components/Search/SearchPageHeader/SearchFilterBar.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/components/Search/SearchRouter/buildSubstitutionsMap.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Search/SearchRouter/getAutocompleteSelectionSubstitutionKey.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Search/SearchRouter/getQueryWithSubstitutions.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Search/SearchRouter/getUpdatedSubstitutionsMap.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Search/SearchRouter/SearchRouter.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Search/SearchRouter/SearchRouter.tsx" "no-restricted-imports" 1 +"../../src/components/Search/SearchSingleSelectionPicker.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/components/Search/SearchTableHeader.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../src/components/SelectionButton.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/SelectionList/BaseSelectionList.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/SelectionList/components/Footer.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/SelectionList/ListItem/SplitListItem.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/SelectionList/SelectionListWithSections/BaseSelectionListWithSections.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/components/SelectionList/SelectionListWithSections/BaseSelectionListWithSections.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/SelectionList/utils/getListboxRole/index.web.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/SelectionScreen.tsx" "no-restricted-imports" 1 +"../../src/components/SettlementButton/AnimatedSettlementButton.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/SettlementButton/index.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../src/components/SettlementButton/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 10 +"../../src/components/SettlementButton/index.tsx" "no-restricted-imports" 1 +"../../src/components/SidePanel/SidePanelOverlay.tsx" "no-restricted-syntax" 1 +"../../src/components/SidePanel/SidePanelReport/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/SignInButtons/AppleAuthWrapper/index.ios.tsx" "import/no-named-as-default" 1 +"../../src/components/SignInButtons/AppleSignIn/index.ios.tsx" "import/no-named-as-default" 1 +"../../src/components/SignInButtons/AppleSignIn/index.tsx" "import/no-named-as-default" 1 +"../../src/components/SignInButtons/IconButton.tsx" "no-restricted-syntax" 1 +"../../src/components/SkeletonViewContentLoader/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/SpendRules/configuration/SpendRuleCategoryBase.tsx" "no-restricted-imports" 1 +"../../src/components/SpendRules/configuration/SpendRuleMaxAmountBase.tsx" "no-restricted-imports" 1 +"../../src/components/SpendRules/configuration/SpendRuleMerchantEditBase.tsx" "no-restricted-imports" 1 +"../../src/components/SpendRules/configuration/SpendRuleMerchantsBase.tsx" "no-restricted-imports" 1 +"../../src/components/SpendRules/configuration/SpendRulesCurrencyBase.tsx" "no-restricted-imports" 1 +"../../src/components/StatePicker/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/StatePicker/StateSelectorModal.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/components/StatePicker/StateSelectorModal.tsx" "no-restricted-imports" 1 +"../../src/components/StateSelector.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/components/SubStepForms/AddressStep.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/components/SubStepForms/ConfirmationStep.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/components/SubStepForms/CountryFullStep.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/SubStepForms/DateOfBirthStep.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/components/SubStepForms/DateOfBirthStep.tsx" "react-hooks/purity" 2 +"../../src/components/SubStepForms/DocusignFullStep.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../src/components/SubStepForms/DocusignFullStep.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/components/SubStepForms/FullNameStep.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/components/SubStepForms/PushRowFieldsStep.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/SubStepForms/RegistrationNumberStep.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/components/SubStepForms/RegistrationNumberStep.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/components/SwipeableView/index.native.tsx" "react-hooks/refs" 2 +"../../src/components/SymbolButton.tsx" "no-restricted-syntax" 1 +"../../src/components/Table/middlewares/filtering.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Table/Table.tsx" "@typescript-eslint/no-unsafe-type-assertion" 4 +"../../src/components/Table/TableBody.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/components/Table/TableContext.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/components/Tables/WorkspaceCategoryRulesTable/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Tables/WorkspaceCompanyCardsTable/WorkspaceCompanyCardsTableRow.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Tables/WorkspaceListTable/TransferOwnershipFlow.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/components/TabSelector/getBackground.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/TagPicker/index.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/components/TagPicker/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/TaxPicker.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Text.tsx" "@typescript-eslint/no-deprecated/fontSize" 1 +"../../src/components/TextInput/BaseTextInput/implementation/index.native.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/TextInput/BaseTextInput/implementation/index.native.tsx" "react-hooks/refs" 1 +"../../src/components/TextInput/BaseTextInput/implementation/index.native.tsx" "react-hooks/static-components" 1 +"../../src/components/TextInput/BaseTextInput/implementation/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/components/TextInput/BaseTextInput/implementation/index.tsx" "import/no-cycle" 1 +"../../src/components/TextInput/BaseTextInput/implementation/index.tsx" "react-hooks/refs" 1 +"../../src/components/TextInput/BaseTextInput/implementation/index.tsx" "react-hooks/static-components" 1 +"../../src/components/TextInput/BaseTextInput/implementations.ts" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/components/TextInput/BaseTextInput/index.tsx" "import/no-cycle" 1 +"../../src/components/TextInput/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/components/TextInput/index.tsx" "import/no-cycle" 1 +"../../src/components/TextInput/index.tsx" "no-restricted-syntax" 1 +"../../src/components/TextInput/TextInputMeasurement/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/TextLinkBlock.tsx" "no-restricted-syntax" 1 +"../../src/components/TextPicker/index.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/components/TextPicker/TextSelectorModal.tsx" "no-restricted-imports" 1 +"../../src/components/TextPicker/TextSelectorModal.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/components/TextWithTooltip/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/ThreeDotsMenu/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/ThreeDotsMenu/index.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/components/TimeModalPicker.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/components/TimeModalPicker.tsx" "no-restricted-imports" 1 +"../../src/components/TimePicker/TimePicker.tsx" "@typescript-eslint/no-unsafe-type-assertion" 6 +"../../src/components/TimePicker/TimePicker.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/components/Tooltip/BaseGenericTooltip/index.native.tsx" "react-hooks/preserve-manual-memoization" 1 +"../../src/components/Tooltip/BaseGenericTooltip/index.native.tsx" "react-hooks/refs" 2 +"../../src/components/Tooltip/BaseGenericTooltip/index.tsx" "react-hooks/immutability" 1 +"../../src/components/Tooltip/BaseGenericTooltip/index.tsx" "react-hooks/refs" 2 +"../../src/components/Tooltip/BaseTooltip/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/components/Tooltip/EducationalTooltip/BaseEducationalTooltip.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Tooltip/GenericTooltip.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/components/Tooltip/PopoverAnchorTooltip.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Tooltip/PopoverAnchorTooltip.tsx" "react-hooks/refs" 7 +"../../src/components/TransactionItemRow/DataCells/ChatBubbleCell.tsx" "rulesdir/no-raw-typography" 2 +"../../src/components/TransactionItemRow/DataCells/MerchantCell.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/UnitPicker.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/components/UploadFile.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/UserInitialsAvatar.tsx" "@typescript-eslint/no-deprecated/fontSize" 1 +"../../src/components/ValidateCodeInput.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/components/ValidateCodeInput.tsx" "react-hooks/set-state-in-effect" 3 +"../../src/components/ValuePicker/index.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/components/ValuePicker/ValueSelectorModal.tsx" "no-restricted-imports" 1 +"../../src/components/VideoPlayer/BaseVideoPlayer.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 4 +"../../src/components/VideoPlayer/BaseVideoPlayer.tsx" "@typescript-eslint/no-unsafe-type-assertion" 7 +"../../src/components/VideoPlayer/BaseVideoPlayer.tsx" "import/no-cycle" 3 +"../../src/components/VideoPlayer/BaseVideoPlayer.tsx" "react-hooks/refs" 13 +"../../src/components/VideoPlayer/BaseVideoPlayer.tsx" "react-hooks/set-state-in-effect" 2 +"../../src/components/VideoPlayer/index.tsx" "import/no-cycle" 2 +"../../src/components/VideoPlayer/useHandleNativeVideoControls/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/VideoPlayer/VideoPlayerControls/index.tsx" "import/no-cycle" 1 +"../../src/components/VideoPlayer/VideoPlayerControls/ProgressBar/index.tsx" "@typescript-eslint/no-deprecated/Gesture.Pan" 2 +"../../src/components/VideoPlayer/VideoPlayerControls/ProgressBar/index.tsx" "@typescript-eslint/no-deprecated/PanGestureChangeEventPayload" 1 +"../../src/components/VideoPlayer/VideoPlayerControls/VolumeButton/index.tsx" "@typescript-eslint/no-deprecated/Gesture.Pan" 2 +"../../src/components/VideoPlayer/VideoPlayerControls/VolumeButton/index.tsx" "@typescript-eslint/no-deprecated/PanGestureChangeEventPayload" 1 +"../../src/components/VideoPlayerContexts/PlaybackContext/playbackContextReportIDUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/VideoPlayerPreview/index.tsx" "import/no-cycle" 1 +"../../src/components/VideoPopoverMenu/index.tsx" "import/no-cycle" 1 +"../../src/components/WalletStatementModal/index.native.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/WideRHPContextProvider/useShouldRenderOverlay.ts" "react-hooks/set-state-in-effect" 1 +"../../src/components/withCurrentUserPersonalDetails.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/withNavigationFallback.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/components/withNavigationTransitionEnd.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/withToggleVisibilityView.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/withViewportOffsetTop.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/WorkspaceConfirmationForm.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 2 +"../../src/components/WorkspaceConfirmationForm.tsx" "no-restricted-imports" 1 +"../../src/components/WorkspaceMemberRoleList.tsx" "no-restricted-imports" 1 +"../../src/components/ZeroWidthView/index.tsx" "no-restricted-syntax" 2 +"../../src/CONFIG.ts" "import/no-named-as-default" 1 +"../../src/CONST/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/CONST/runtimeConfigured.ts" "import/no-named-as-default" 1 +"../../src/CONST/runtimeConfigured.ts" "rulesdir/no-direct-personal-details-list" 1 +"../../src/DeepLinkHandler.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/DeepLinkHandler.tsx" "no-restricted-syntax" 1 +"../../src/Expensify.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/Expensify.tsx" "no-restricted-syntax" 1 +"../../src/Expensify.tsx" "react-hooks/refs" 1 +"../../src/Expensify.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/GlobalModals.tsx" "no-restricted-syntax" 2 +"../../src/hooks/useAccountIndicatorChecks.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/hooks/useAdvancedSearchFilters.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/hooks/useAnimatedHighlightStyle/index.ts" "react-hooks/set-state-in-effect" 2 +"../../src/hooks/useAssignCard.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/hooks/useAutocompleteSuggestions.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/hooks/useAutoCreateTrackWorkspace.ts" "no-restricted-imports" 1 +"../../src/hooks/useAutoUpdateTimezone.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/hooks/useBulkDuplicateAction.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/hooks/useBulkDuplicateAction.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/hooks/useBulkPayOptions.ts" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/hooks/useCachedImageSource.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/hooks/useCachedImageSource.ts" "react-hooks/set-state-in-effect" 1 +"../../src/hooks/useCancellationType.ts" "react-hooks/refs" 2 +"../../src/hooks/useCancellationType.ts" "react-hooks/set-state-in-effect" 1 +"../../src/hooks/useCardFeedsForActivePolicies.ts" "no-restricted-imports" 1 +"../../src/hooks/useCardFeedsForDisplay.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/hooks/useCardFeedsForDisplay.ts" "no-restricted-imports" 1 +"../../src/hooks/useChangeTransactionsReportReports.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/hooks/useCurrentReportID.tsx" "react-hooks/preserve-manual-memoization" 1 +"../../src/hooks/useDebounce.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/hooks/useDebouncedValue.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/hooks/useDebounceNonReactive.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/hooks/useDragAndDrop/types.ts" "@typescript-eslint/no-deprecated/React.MutableRefObject" 1 +"../../src/hooks/useDynamicBackPath.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/hooks/useDynamicBackPath.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/hooks/useDynamicForwardPath.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/hooks/useExportedToFilterOptions.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/hooks/useFilterFormValues.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/hooks/useHtmlPaste/index.ts" "react-hooks/preserve-manual-memoization" 1 +"../../src/hooks/useHydrateReportsFromSnapshot.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/hooks/useImportSpreadsheetConfirmModal.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/hooks/useInitial.ts" "react-hooks/refs" 2 +"../../src/hooks/useInitialSelection.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/hooks/useIsBlockedToAddFeed.ts" "react-hooks/set-state-in-effect" 1 +"../../src/hooks/useIsOwnWorkspaceChatRef.ts" "react-hooks/refs" 2 +"../../src/hooks/useIsPaidPolicyAdmin.ts" "no-restricted-imports" 1 +"../../src/hooks/useKeyboardShortcut.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/hooks/useLazyAsset.ts" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/hooks/useLazyAsset.ts" "react-hooks/set-state-in-effect" 1 +"../../src/hooks/useMarkdownStyle.ts" "rulesdir/no-raw-typography" 3 +"../../src/hooks/useNewTransactions.ts" "react-hooks/preserve-manual-memoization" 3 +"../../src/hooks/useNewTransactions.ts" "react-hooks/refs" 2 +"../../src/hooks/useOnyx.ts" "@typescript-eslint/no-unsafe-type-assertion" 10 +"../../src/hooks/useOptimisticDraftTransactions.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/hooks/useOtherFeedsForFeedSelector.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/hooks/usePageRefresh/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/hooks/useParticipantSubmission.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/hooks/usePaymentOptions.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../src/hooks/usePaymentOptions.ts" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/hooks/usePaymentOptions.ts" "react-hooks/preserve-manual-memoization" 6 +"../../src/hooks/usePaymentOptions.ts" "react-hooks/refs" 1 +"../../src/hooks/usePersonalPolicy.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/hooks/usePolicyData/index.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/hooks/usePolicyData/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/hooks/usePreferredCurrency.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/hooks/usePrevious.ts" "react-hooks/refs" 1 +"../../src/hooks/useReportScrollManager/index.native.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/hooks/useResetBankAccountModal.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/hooks/useResponsiveLayout/index.native.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/hooks/useResponsiveLayout/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/hooks/useRestoreWorkspacesTabOnNavigate.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/hooks/useReviewDuplicatesNavigation.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/hooks/useReviewDuplicatesNavigation.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/hooks/useSearchBulkActions.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../src/hooks/useSearchBulkActions.ts" "@typescript-eslint/no-unsafe-type-assertion" 9 +"../../src/hooks/useSidebarOrderedReports.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/hooks/useSidebarOrderedReports.tsx" "react-hooks/preserve-manual-memoization" 5 +"../../src/hooks/useSidebarOrderedReports.tsx" "react-hooks/refs" 5 +"../../src/hooks/useSidebarOrderedReports.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/hooks/useSingleExecution/index.native.ts" "react-hooks/refs" 1 +"../../src/hooks/useStableIndexedHandler.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/hooks/useStableIndexedHandler.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/hooks/useStepFormSubmit.ts" "no-restricted-syntax" 1 +"../../src/hooks/useSubPage/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/hooks/useThemePreference.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/hooks/useTransactionViolationOfWorkspace.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/hooks/useViolations.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/hooks/useWindowDimensions/index.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/hooks/useWorkletStateMachine/index.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/hooks/useWorkletStateMachine/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/languages/flattenObject.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/AccountingUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/actions/__mocks__/App.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/actions/__mocks__/App.ts" "no-restricted-syntax" 2 +"../../src/libs/actions/__mocks__/OnyxUpdates.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/App.ts" "import/no-cycle" 1 +"../../src/libs/actions/App.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/applyOnyxUpdatesReliably.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/BankAccounts.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/actions/BankAccounts.ts" "import/no-cycle" 1 +"../../src/libs/actions/BankAccounts.ts" "no-restricted-syntax" 2 +"../../src/libs/actions/CanvasSize.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/Card.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/actions/Card.ts" "no-restricted-syntax" 2 +"../../src/libs/actions/Chronos.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/clearOnyxAndSeedFullReconnect.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/actions/ClearReportActionErrors.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/actions/CompanyCards.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/libs/actions/CompanyCards.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/actions/CompanyCards.ts" "no-restricted-imports" 1 +"../../src/libs/actions/CompanyCards.ts" "no-restricted-syntax" 4 +"../../src/libs/actions/connections/index.ts" "import/no-cycle" 1 +"../../src/libs/actions/connections/index.ts" "no-restricted-syntax" 2 +"../../src/libs/actions/connections/merge/index.ts" "import/no-cycle" 1 +"../../src/libs/actions/connections/NetSuiteCommands.ts" "no-restricted-syntax" 2 +"../../src/libs/actions/connections/QuickbooksDesktop.ts" "no-restricted-syntax" 2 +"../../src/libs/actions/connections/QuickbooksOnline.ts" "no-restricted-syntax" 2 +"../../src/libs/actions/connections/SageIntacct.ts" "no-restricted-syntax" 2 +"../../src/libs/actions/connections/Xero.ts" "no-restricted-syntax" 2 +"../../src/libs/actions/Delegate.ts" "no-restricted-syntax" 4 +"../../src/libs/actions/Delegate.ts" "rulesdir/no-direct-personal-details-list" 1 +"../../src/libs/actions/Domain.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/ExitSurvey.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/getCompanyCardBankConnection/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 4 +"../../src/libs/actions/getCompanyCardBankConnection/index.tsx" "no-restricted-imports" 1 +"../../src/libs/actions/getCompanyCardBankConnection/index.tsx" "no-restricted-syntax" 2 +"../../src/libs/actions/Help.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/HybridApp/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/actions/ImportTransactions.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/libs/actions/ImportTransactions.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/actions/ImportTransactions.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/InputFocus/index.web.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/IOU/BulkEdit.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/actions/IOU/BulkEdit.ts" "no-restricted-syntax" 2 +"../../src/libs/actions/IOU/DeleteMoneyRequest.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/actions/IOU/DeleteMoneyRequest.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/IOU/Duplicate.ts" "@typescript-eslint/no-unsafe-type-assertion" 7 +"../../src/libs/actions/IOU/Duplicate.ts" "no-restricted-syntax" 2 +"../../src/libs/actions/IOU/Hold.ts" "@typescript-eslint/no-unnecessary-type-assertion" 3 +"../../src/libs/actions/IOU/Hold.ts" "no-restricted-syntax" 2 +"../../src/libs/actions/IOU/index.ts" "rulesdir/no-onyx-connect" 7 +"../../src/libs/actions/IOU/MoneyRequest.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/actions/IOU/MoneyRequestBuilder.ts" "@typescript-eslint/no-deprecated/getReportTransactions" 2 +"../../src/libs/actions/IOU/MoneyRequestBuilder.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../src/libs/actions/IOU/MoneyRequestBuilder.ts" "@typescript-eslint/no-deprecated/translateLocal" 1 +"../../src/libs/actions/IOU/MoneyRequestBuilder.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/actions/IOU/PayMoneyRequest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/libs/actions/IOU/PayMoneyRequest.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/actions/IOU/PayMoneyRequest.ts" "no-restricted-imports" 1 +"../../src/libs/actions/IOU/PayMoneyRequest.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/IOU/PerDiem.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/libs/actions/IOU/PerDiem.ts" "no-restricted-syntax" 2 +"../../src/libs/actions/IOU/Receipt.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/IOU/RejectMoneyRequest.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/actions/IOU/RejectMoneyRequest.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/IOU/ReportWorkflow.ts" "@typescript-eslint/no-deprecated/getReportTransactions" 2 +"../../src/libs/actions/IOU/ReportWorkflow.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/IOU/SearchUpdate.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../src/libs/actions/IOU/SendInvoice.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/actions/IOU/SendInvoice.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/IOU/SendMoney.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/actions/IOU/SendMoney.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/IOU/Split.ts" "@typescript-eslint/no-deprecated/Localize.translateLocal" 2 +"../../src/libs/actions/IOU/Split.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/actions/IOU/Split.ts" "no-restricted-syntax" 3 +"../../src/libs/actions/IOU/SplitExpenseItems.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/actions/IOU/SplitTransactionUpdate.ts" "@typescript-eslint/no-unnecessary-type-assertion" 3 +"../../src/libs/actions/IOU/SplitTransactionUpdate.ts" "@typescript-eslint/no-unsafe-type-assertion" 20 +"../../src/libs/actions/IOU/StartExpenseFlows.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/actions/IOU/TrackExpense.ts" "@typescript-eslint/no-unnecessary-type-assertion" 4 +"../../src/libs/actions/IOU/TrackExpense.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/actions/IOU/TrackExpense.ts" "no-restricted-syntax" 2 +"../../src/libs/actions/IOU/UpdateMoneyRequest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../src/libs/actions/IOU/UpdateMoneyRequest.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/actions/IOU/UpdateMoneyRequest.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/Link.ts" "@typescript-eslint/no-unsafe-type-assertion" 4 +"../../src/libs/actions/Link.ts" "no-restricted-syntax" 2 +"../../src/libs/actions/MapboxToken.ts" "no-restricted-syntax" 2 +"../../src/libs/actions/MergeAccounts.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/MergeTransaction.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/actions/MergeTransaction.ts" "no-restricted-imports" 1 +"../../src/libs/actions/MergeTransaction.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/OdometerTransactionUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/actions/OnyxDerived/configs/cardFeedErrors.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/libs/actions/OnyxDerived/configs/guideAccountIDs.ts" "rulesdir/no-direct-personal-details-list" 1 +"../../src/libs/actions/OnyxDerived/configs/loginToAccountIDMap.ts" "rulesdir/no-direct-personal-details-list" 1 +"../../src/libs/actions/OnyxDerived/configs/reportAttributes.ts" "rulesdir/no-direct-personal-details-list" 2 +"../../src/libs/actions/OnyxDerived/index.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../src/libs/actions/OnyxDerived/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/actions/OnyxUpdateManager/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/libs/actions/OnyxUpdateManager/utils/__mocks__/applyUpdates.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/OnyxUpdateManager/utils/applyUpdates.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/OnyxUpdateManager/utils/DeferredOnyxUpdates.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/actions/OnyxUpdateManager/utils/DeferredOnyxUpdates.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/OnyxUpdates.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/actions/PaymentMethods.ts" "no-restricted-syntax" 2 +"../../src/libs/actions/PersistedRequests.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../src/libs/actions/PersonalCards.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/libs/actions/PersonalCards.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/actions/PersonalDetails.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/actions/PersonalDetails.ts" "no-restricted-syntax" 5 +"../../src/libs/actions/Plaid.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/Policy/Category.ts" "no-restricted-syntax" 5 +"../../src/libs/actions/Policy/DistanceRate.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../src/libs/actions/Policy/DistanceRate.ts" "no-restricted-syntax" 2 +"../../src/libs/actions/Policy/Member.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/actions/Policy/Member.ts" "no-restricted-imports" 1 +"../../src/libs/actions/Policy/Member.ts" "no-restricted-syntax" 7 +"../../src/libs/actions/Policy/Member.ts" "rulesdir/no-direct-personal-details-list" 1 +"../../src/libs/actions/Policy/Member.ts" "rulesdir/no-onyx-connect" 1 +"../../src/libs/actions/Policy/PerDiem.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/Policy/Plan.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/Policy/Policy.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../src/libs/actions/Policy/Policy.ts" "@typescript-eslint/no-unsafe-type-assertion" 8 +"../../src/libs/actions/Policy/Policy.ts" "no-restricted-imports" 2 +"../../src/libs/actions/Policy/Policy.ts" "no-restricted-syntax" 9 +"../../src/libs/actions/Policy/ReportField.ts" "no-restricted-imports" 1 +"../../src/libs/actions/Policy/ReportField.ts" "no-restricted-syntax" 4 +"../../src/libs/actions/Policy/Rules.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/Policy/Tag.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/actions/Policy/Tag.ts" "no-restricted-imports" 1 +"../../src/libs/actions/Policy/Tag.ts" "no-restricted-syntax" 4 +"../../src/libs/actions/Policy/Travel.ts" "no-restricted-syntax" 2 +"../../src/libs/actions/PolicyConnections.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/QueuedOnyxUpdates.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/actions/ReimbursementAccount/resetNonUSDBankAccount.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/ReimbursementAccount/resetUSDBankAccount.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/replaceOptimisticReportWithActualReport.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/actions/Report/index.ts" "@typescript-eslint/no-deprecated/getReportTransactions" 1 +"../../src/libs/actions/Report/index.ts" "@typescript-eslint/no-deprecated/originalMessage" 11 +"../../src/libs/actions/Report/index.ts" "@typescript-eslint/no-deprecated/reportAction.originalMessage" 1 +"../../src/libs/actions/Report/index.ts" "@typescript-eslint/no-deprecated/videoAttributeCache" 1 +"../../src/libs/actions/Report/index.ts" "@typescript-eslint/no-unnecessary-type-assertion" 5 +"../../src/libs/actions/Report/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 19 +"../../src/libs/actions/Report/index.ts" "no-restricted-syntax" 7 +"../../src/libs/actions/Report/index.ts" "rulesdir/no-onyx-connect" 2 +"../../src/libs/actions/Report/index.ts" "unicorn/prefer-string-replace-all" 1 +"../../src/libs/actions/Report/MarkAllMessageAsRead.tsx" "no-restricted-syntax" 1 +"../../src/libs/actions/ReportLayout.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/actions/ReportLayout.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/RequestConflictUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/actions/ScheduleCall.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/Search.ts" "@typescript-eslint/no-misused-promises" 1 +"../../src/libs/actions/Search.ts" "@typescript-eslint/no-unnecessary-type-assertion" 6 +"../../src/libs/actions/Search.ts" "@typescript-eslint/no-unsafe-type-assertion" 16 +"../../src/libs/actions/Search.ts" "import/no-cycle" 1 +"../../src/libs/actions/Session/index.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/libs/actions/Session/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/libs/actions/Session/index.ts" "no-restricted-syntax" 9 +"../../src/libs/actions/Session/index.ts" "rulesdir/no-onyx-connect" 2 +"../../src/libs/actions/SplitExpenses.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/actions/StatsCounter.ts" "no-restricted-syntax" 2 +"../../src/libs/actions/StoreReview/index.native.ts" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/libs/actions/Subscription.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/actions/Subscription.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/Tab.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/actions/Task.ts" "@typescript-eslint/no-unnecessary-type-assertion" 8 +"../../src/libs/actions/Task.ts" "import/no-cycle" 6 +"../../src/libs/actions/Task.ts" "no-restricted-imports" 1 +"../../src/libs/actions/Task.ts" "no-restricted-syntax" 5 +"../../src/libs/actions/TaskDeletion.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/libs/actions/TaxRate.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/TeachersUnite.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/Transaction.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/libs/actions/Transaction.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/actions/Transaction.ts" "no-restricted-syntax" 2 +"../../src/libs/actions/TransactionEdit.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/actions/Travel.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/TravelBilling.ts" "no-restricted-syntax" 3 +"../../src/libs/actions/UnreportedExpenses.tsx" "no-restricted-syntax" 1 +"../../src/libs/actions/User.ts" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/libs/actions/User.ts" "no-restricted-imports" 1 +"../../src/libs/actions/User.ts" "no-restricted-syntax" 7 +"../../src/libs/actions/VacationDelegate.ts" "no-restricted-syntax" 2 +"../../src/libs/actions/Wallet.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../src/libs/actions/Wallet.ts" "@typescript-eslint/no-unsafe-type-assertion" 4 +"../../src/libs/actions/Wallet.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/Welcome/index.ts" "import/no-cycle" 2 +"../../src/libs/actions/Welcome/index.ts" "no-restricted-syntax" 1 +"../../src/libs/actions/Welcome/OnboardingFlow.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/libs/actions/Welcome/OnboardingFlow.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/actions/Welcome/OnboardingFlow.ts" "import/no-cycle" 2 +"../../src/libs/actions/Workflow.ts" "import/no-cycle" 3 +"../../src/libs/API/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/API/makeRequest.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/AttendeeUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/libs/BankAccountUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/CardFeedUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 7 +"../../src/libs/CardFeedUtils.ts" "import/no-cycle" 2 +"../../src/libs/CardUtils.ts" "@typescript-eslint/no-unnecessary-type-assertion" 3 +"../../src/libs/CardUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 12 +"../../src/libs/CategoryOptionListUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/ChronosUtils.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/libs/clearSelectedTextIfComposerBlurred/clearSelectedTextIfComposerBlurred.web.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Clipboard/index.ts" "@typescript-eslint/no-deprecated/document.execCommand" 1 +"../../src/libs/Clipboard/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Clipboard/index.ts" "no-restricted-syntax" 1 +"../../src/libs/ComposerFocusManager.ts" "@typescript-eslint/no-deprecated/TextInput.State.currentlyFocusedField" 1 +"../../src/libs/ComposerFocusManager.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/ComposerUtils/index.ts" "no-restricted-syntax" 1 +"../../src/libs/CountryUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/CurrencyUtils.ts" "rulesdir/no-onyx-connect" 1 +"../../src/libs/DateUtils.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/libs/DateUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/libs/DebugUtils.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/libs/DebugUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 20 +"../../src/libs/DistanceRequestUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/DistanceRequestUtils.ts" "import/no-cycle" 2 +"../../src/libs/DomUtils/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/EmojiUtils.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Environment/Environment.ts" "import/no-named-as-default" 1 +"../../src/libs/Environment/getEnvironment/index.native.ts" "import/no-named-as-default" 1 +"../../src/libs/Environment/getEnvironment/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Environment/getEnvironment/index.ts" "import/no-named-as-default" 1 +"../../src/libs/ErrorUtils.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../src/libs/ErrorUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 4 +"../../src/libs/ExpenseRuleUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/ExpensifyCardFeedSelectorUtils.ts" "import/no-cycle" 1 +"../../src/libs/ExportOnyxState/masking.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/libs/ExportOnyxState/masking.ts" "@typescript-eslint/no-unsafe-type-assertion" 14 +"../../src/libs/fileDownload/DownloadUtils.ts" "no-restricted-syntax" 1 +"../../src/libs/fileDownload/FileUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 4 +"../../src/libs/fileDownload/getImageManipulator/index.native.ts" "@typescript-eslint/no-deprecated/manipulateAsync" 1 +"../../src/libs/fileDownload/getImageManipulator/index.ts" "@typescript-eslint/no-deprecated/manipulateAsync" 1 +"../../src/libs/fileDownload/getImageResolution.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/fileDownload/heicConverter/index.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../src/libs/fileDownload/index.android.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/fileDownload/index.ios.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/filterArrayByMatch.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/focusComposerWithDelay/index.ts" "no-restricted-syntax" 1 +"../../src/libs/focusComposerWithDelay/setTextInputSelection.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/Formula.ts" "@typescript-eslint/no-deprecated/getReportTransactions" 1 +"../../src/libs/Fullstory/common.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/getPlatform/index.ts" "no-restricted-syntax" 1 +"../../src/libs/getPolicyFromSearchData.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/libs/getReturnValue.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/hadTabNavigation.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/HttpUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/libs/ImportOnyxStateUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 7 +"../../src/libs/interceptAnonymousUser.ts" "no-restricted-syntax" 1 +"../../src/libs/KeyboardShortcut/bindHandlerToKeydownEvent/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/KeyboardShortcut/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/libs/KeyboardShortcut/isEnterWhileComposition.ts" "@typescript-eslint/no-deprecated/event.keyCode" 1 +"../../src/libs/KeyboardShortcut/isEnterWhileComposition.ts" "no-restricted-syntax" 1 +"../../src/libs/LocalePhoneNumber.ts" "rulesdir/no-onyx-connect" 1 +"../../src/libs/Localize/index.ts" "@typescript-eslint/no-deprecated/translateLocal" 1 +"../../src/libs/Localize/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/Log.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/LoginUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/mapChildrenFlat.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/memoize/cache/ArrayCache.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/memoize/index.ts" "@typescript-eslint/no-unsafe-return" 3 +"../../src/libs/memoize/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 8 +"../../src/libs/memoize/utils.ts" "@typescript-eslint/no-unsafe-type-assertion" 7 +"../../src/libs/merge/HRUtils.ts" "import/no-cycle" 1 +"../../src/libs/MergeTransactionUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 5 +"../../src/libs/Middleware/HandleUnusedOptimisticID.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/libs/Middleware/HandleUnusedOptimisticID.ts" "@typescript-eslint/no-unsafe-type-assertion" 4 +"../../src/libs/Middleware/HandleUnusedOptimisticID.ts" "no-restricted-syntax" 1 +"../../src/libs/Middleware/HandleUnusedOptimisticID.ts" "rulesdir/no-direct-personal-details-list" 1 +"../../src/libs/Middleware/Logging.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Middleware/Pagination.ts" "@typescript-eslint/no-unsafe-type-assertion" 5 +"../../src/libs/Middleware/Reauthentication.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/libs/Middleware/RecordFullReconnectTime.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/libs/Middleware/SaveResponseInOnyx.ts" "no-restricted-syntax" 1 +"../../src/libs/models/BankAccount.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/ModifiedExpenseMessage.ts" "@typescript-eslint/no-unsafe-type-assertion" 6 +"../../src/libs/ModifiedExpenseMessage.ts" "import/no-cycle" 3 +"../../src/libs/MoneyRequestReportUtils.ts" "no-restricted-imports" 1 +"../../src/libs/MoneyRequestUtils.ts" "import/no-cycle" 2 +"../../src/libs/Navigation/AppNavigator/AuthScreens.tsx" "no-restricted-syntax" 1 +"../../src/libs/Navigation/AppNavigator/AuthScreensInitHandler.tsx" "no-restricted-syntax" 6 +"../../src/libs/Navigation/AppNavigator/AuthScreensInitHandler.tsx" "react-hooks/refs" 1 +"../../src/libs/Navigation/AppNavigator/createRootStackNavigator/GetStateForActionHandlers.ts" "@typescript-eslint/no-unnecessary-type-assertion" 4 +"../../src/libs/Navigation/AppNavigator/createRootStackNavigator/GetStateForActionHandlers.ts" "@typescript-eslint/no-unsafe-type-assertion" 13 +"../../src/libs/Navigation/AppNavigator/createRootStackNavigator/useCustomRootStackNavigatorState/restoreTabNavigatorRoutes.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Navigation/AppNavigator/createSplitNavigator/SplitRouter.ts" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/libs/Navigation/AppNavigator/createSplitNavigator/usePreserveNavigatorState.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/libs/Navigation/AppNavigator/createSplitNavigator/usePreserveNavigatorState.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Navigation/AppNavigator/createWorkspaceNavigator/WorkspaceRouter.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Navigation/AppNavigator/enhanceCardStyleInterpolator.ts" "@typescript-eslint/no-unnecessary-type-assertion" 4 +"../../src/libs/Navigation/AppNavigator/FreezeWrapper/index.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/libs/Navigation/AppNavigator/KeyboardShortcutsHandler/EscapeHandler.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/libs/Navigation/AppNavigator/KeyboardShortcutsHandler/NewChatHandler.tsx" "no-restricted-syntax" 2 +"../../src/libs/Navigation/AppNavigator/KeyboardShortcutsHandler/SearchHandler.tsx" "no-restricted-syntax" 1 +"../../src/libs/Navigation/AppNavigator/KeyboardShortcutsHandler/ShortcutsOverviewHandler.tsx" "no-restricted-syntax" 1 +"../../src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 5 +"../../src/libs/Navigation/AppNavigator/Navigators/Overlay/BaseOverlay.tsx" "no-restricted-syntax" 2 +"../../src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx" "no-restricted-syntax" 1 +"../../src/libs/Navigation/AppNavigator/Navigators/SearchFullscreenNavigator.tsx" "no-restricted-syntax" 1 +"../../src/libs/Navigation/AppNavigator/Navigators/SettingsSplitNavigator.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Navigation/AppNavigator/Navigators/TestToolsModalNavigator.tsx" "no-restricted-syntax" 1 +"../../src/libs/Navigation/AppNavigator/Navigators/WorkspaceSplitNavigator.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Navigation/AppNavigator/PublicScreens.tsx" "import/no-named-as-default" 1 +"../../src/libs/Navigation/AppNavigator/routerExtensions/addPushParamsRouterExtension.ts" "@typescript-eslint/no-unsafe-type-assertion" 4 +"../../src/libs/Navigation/AppNavigator/routerExtensions/addRootHistoryRouterExtension.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Navigation/AppNavigator/routerExtensions/addRootHistoryRouterExtensionUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Navigation/DebugTabNavigator.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Navigation/guards/MigratedUserWelcomeModalGuard.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Navigation/guards/OnboardingGuard.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../src/libs/Navigation/guards/OnboardingGuard.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/Navigation/helpers/buildTabNavigatorNestedState.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/libs/Navigation/helpers/createNormalizedConfigs.ts" "@typescript-eslint/no-deprecated/escape" 1 +"../../src/libs/Navigation/helpers/createNormalizedConfigs.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Navigation/helpers/dynamicRoutesUtils/createDynamicRoute.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/Navigation/helpers/dynamicRoutesUtils/getDynamicRouteAdaptedState.ts" "@typescript-eslint/no-unnecessary-type-assertion" 5 +"../../src/libs/Navigation/helpers/dynamicRoutesUtils/getDynamicRouteAdaptedState.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Navigation/helpers/dynamicRoutesUtils/getDynamicRouteQueryParams.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Navigation/helpers/dynamicRoutesUtils/getPathWithoutDynamicSuffix.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Navigation/helpers/findFocusedRouteWithOnyxTabGuard.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/libs/Navigation/helpers/getAdaptedStateFromPath.ts" "@typescript-eslint/no-unnecessary-type-assertion" 3 +"../../src/libs/Navigation/helpers/getAdaptedStateFromPath.ts" "@typescript-eslint/no-unsafe-type-assertion" 4 +"../../src/libs/Navigation/helpers/getAdaptedStateFromPath.ts" "import/no-cycle" 1 +"../../src/libs/Navigation/helpers/getOnboardingRouteFromScreen.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/Navigation/helpers/getParamsFromRoute.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/Navigation/helpers/getPathFromState.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/libs/Navigation/helpers/getPathFromState.ts" "@typescript-eslint/no-unsafe-type-assertion" 6 +"../../src/libs/Navigation/helpers/getStateFromPath.ts" "@typescript-eslint/no-unsafe-type-assertion" 4 +"../../src/libs/Navigation/helpers/getTopmostFullScreenRoute.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Navigation/helpers/getTopmostReportParams.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Navigation/helpers/goBackFromWorkspaceSettingPages.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Navigation/helpers/isNavigatorName.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Navigation/helpers/isRHPOnSearchMoneyRequestReportPage.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Navigation/helpers/isTabRouteAtRoot.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Navigation/helpers/lastVisitedTabPathUtils/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Navigation/helpers/linkTo/index.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../src/libs/Navigation/helpers/linkTo/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 15 +"../../src/libs/Navigation/helpers/navigateToWorkspacesPage.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/libs/Navigation/helpers/navigateToWorkspacesPage.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Navigation/helpers/popReportsSplitNavigatorToReport.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Navigation/helpers/replaceCompanyCardsRoute.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Navigation/helpers/swapBackgroundTabForRHPTarget.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/libs/Navigation/helpers/swapBackgroundTabForRHPTarget.ts" "@typescript-eslint/no-unsafe-type-assertion" 4 +"../../src/libs/Navigation/helpers/tabNavigatorUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/Navigation/helpers/useIsSidebarRouteActive.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Navigation/linkingConfig/config.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/libs/Navigation/linkingConfig/config.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/Navigation/linkingConfig/RELATIONS/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/libs/Navigation/Navigation.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/libs/Navigation/Navigation.ts" "@typescript-eslint/no-unsafe-type-assertion" 10 +"../../src/libs/Navigation/PlatformStackNavigation/createPlatformStackNavigatorComponent/index.native.tsx" "@typescript-eslint/no-deprecated/NavigationContent" 1 +"../../src/libs/Navigation/PlatformStackNavigation/createPlatformStackNavigatorComponent/index.tsx" "@typescript-eslint/no-deprecated/NavigationContent" 1 +"../../src/libs/Navigation/PlatformStackNavigation/navigationOptions/animation/withAnimation.ts" "@typescript-eslint/no-unsafe-type-assertion" 6 +"../../src/libs/Navigation/PlatformStackNavigation/ScreenLayout.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/NetSuiteUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Network/enhanceParameters.ts" "no-restricted-syntax" 1 +"../../src/libs/Network/index.ts" "no-restricted-syntax" 1 +"../../src/libs/Network/LoadTestState.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Network/SequentialQueue.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Notification/LocalNotification/BrowserNotifications.ts" "@typescript-eslint/no-deprecated/translateLocal" 1 +"../../src/libs/Notification/LocalNotification/BrowserNotifications.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Notification/LocalNotification/BrowserNotifications.ts" "no-restricted-imports" 1 +"../../src/libs/Notification/LocalNotification/BrowserNotifications.ts" "no-restricted-syntax" 1 +"../../src/libs/Notification/PushNotification/parsePushNotificationPayload.ts" "@typescript-eslint/no-unsafe-type-assertion" 5 +"../../src/libs/Notification/PushNotification/shouldShowPushNotification.ts" "no-restricted-syntax" 2 +"../../src/libs/Notification/PushNotification/subscribeToPushNotifications.ts" "no-restricted-syntax" 1 +"../../src/libs/ObjectUtils.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/libs/ObjectUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/OptionsListUtils/index.ts" "@typescript-eslint/no-deprecated/translateLocal" 2 +"../../src/libs/OptionsListUtils/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 4 +"../../src/libs/PageHTMLCapture/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Parser.ts" "rulesdir/no-onyx-connect" 1 +"../../src/libs/ParsingUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/PaymentUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/PersonalDetailsUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/PersonalDetailsUtils.ts" "unicorn/prefer-at" 1 +"../../src/libs/PhoneNumber.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/PolicyUtils/approvals.ts" "import/no-cycle" 3 +"../../src/libs/PolicyUtils/connections.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/libs/PolicyUtils/connections.ts" "@typescript-eslint/no-unsafe-type-assertion" 4 +"../../src/libs/PolicyUtils/connections.ts" "import/no-cycle" 1 +"../../src/libs/PolicyUtils/domains.ts" "import/no-cycle" 1 +"../../src/libs/PolicyUtils/errors.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/PolicyUtils/errors.ts" "import/no-cycle" 3 +"../../src/libs/PolicyUtils/features.ts" "import/no-cycle" 2 +"../../src/libs/PolicyUtils/index.ts" "import/no-cycle" 9 +"../../src/libs/PolicyUtils/members.ts" "import/no-cycle" 2 +"../../src/libs/PolicyUtils/policy.ts" "import/no-cycle" 2 +"../../src/libs/PolicyUtils/rules.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/PolicyUtils/tag.ts" "import/no-cycle" 1 +"../../src/libs/PolicyUtils/travel.ts" "import/no-cycle" 2 +"../../src/libs/prepareRequestPayload/index.native.ts" "@typescript-eslint/no-unsafe-type-assertion" 5 +"../../src/libs/prepareRequestPayload/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Pusher/index.native.ts" "@typescript-eslint/no-unsafe-type-assertion" 6 +"../../src/libs/Pusher/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/QuickActionUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/ReceiptUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/ReportActionMessageUtils.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/libs/ReportActionsUtils.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../src/libs/ReportActionsUtils.ts" "@typescript-eslint/no-deprecated/reportAction.sequenceNumber" 1 +"../../src/libs/ReportActionsUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 109 +"../../src/libs/ReportActionsUtils.ts" "import/no-cycle" 1 +"../../src/libs/ReportActionsUtils.ts" "rulesdir/no-onyx-connect" 2 +"../../src/libs/ReportAlternateTextUtils.ts" "@typescript-eslint/no-deprecated/getReportTransactions" 1 +"../../src/libs/ReportNameUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/ReportNameUtils.ts" "import/no-cycle" 4 +"../../src/libs/ReportPrimaryActionUtils.ts" "no-restricted-imports" 1 +"../../src/libs/ReportTitleUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/ReportUtils.ts" "@typescript-eslint/no-deprecated/getPolicy" 27 +"../../src/libs/ReportUtils.ts" "@typescript-eslint/no-deprecated/getReportTransactions" 16 +"../../src/libs/ReportUtils.ts" "@typescript-eslint/no-deprecated/originalMessage" 23 +"../../src/libs/ReportUtils.ts" "@typescript-eslint/no-deprecated/translateLocal" 7 +"../../src/libs/ReportUtils.ts" "@typescript-eslint/no-unnecessary-type-assertion" 15 +"../../src/libs/ReportUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 31 +"../../src/libs/ReportUtils.ts" "import/no-cycle" 5 +"../../src/libs/ReportUtils.ts" "rulesdir/no-onyx-connect" 13 +"../../src/libs/sanitizeLogParams.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/SearchQueryUtils.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../src/libs/SearchQueryUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 48 +"../../src/libs/SearchQueryUtils.ts" "import/no-cycle" 4 +"../../src/libs/SearchQueryUtils.ts" "unicorn/prefer-string-replace-all" 1 +"../../src/libs/SearchUIUtils.ts" "@typescript-eslint/no-unnecessary-type-assertion" 4 +"../../src/libs/SearchUIUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 71 +"../../src/libs/SearchUIUtils.ts" "import/no-cycle" 1 +"../../src/libs/SearchUIUtils.ts" "no-restricted-imports" 1 +"../../src/libs/ShortcutManager/index.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/libs/SidebarUtils.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/libs/SidebarUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Sound/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/libs/SpendRulesUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/libs/StatusBar/index.android.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/StatusBar/index.web.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/StatusBar/types.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/StringUtils/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/telemetry/forwardLogsToSentry.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/telemetry/TelemetrySynchronizer.ts" "rulesdir/no-direct-personal-details-list" 1 +"../../src/libs/TransactionPreviewUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/TransactionUtils/buildOptimisticTransaction.ts" "import/no-cycle" 1 +"../../src/libs/TransactionUtils/duplicates.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../src/libs/TransactionUtils/duplicates.ts" "@typescript-eslint/no-unsafe-type-assertion" 4 +"../../src/libs/TransactionUtils/duplicates.ts" "import/no-cycle" 2 +"../../src/libs/TransactionUtils/getUpdatedTransaction.ts" "@typescript-eslint/no-deprecated/translateLocal" 2 +"../../src/libs/TransactionUtils/getUpdatedTransaction.ts" "import/no-cycle" 2 +"../../src/libs/TransactionUtils/index.ts" "@typescript-eslint/no-deprecated/getReportTransactions" 1 +"../../src/libs/TransactionUtils/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/libs/TransactionUtils/index.ts" "import/no-cycle" 7 +"../../src/libs/TransactionUtils/tax.ts" "@typescript-eslint/no-deprecated/translateLocal" 1 +"../../src/libs/TransactionUtils/tax.ts" "import/no-cycle" 2 +"../../src/libs/TransactionUtils/violations.ts" "import/no-cycle" 2 +"../../src/libs/UnreadIndicatorUpdater/index.ts" "no-restricted-imports" 1 +"../../src/libs/UnreadIndicatorUpdater/index.ts" "no-restricted-syntax" 1 +"../../src/libs/updateMultilineInputRange/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Url.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/UserAvatarUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/libs/ValidationUtils.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/libs/ValidationUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/Violations/ViolationsUtils.ts" "import/no-cycle" 4 +"../../src/libs/Violations/ViolationsUtils.ts" "no-restricted-syntax" 1 +"../../src/libs/Wallet/index.ios.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/WorkflowUtils.ts" "import/no-cycle" 2 +"../../src/libs/WorkspaceReportFieldUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/WorkspacesSettingsUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/ONYXKEYS.ts" "no-restricted-syntax" 2 +"../../src/pages/AddExistingExpense.tsx" "no-restricted-imports" 1 +"../../src/pages/AddPersonalBankAccountPage/substeps/AddressStep.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/AddPersonalBankAccountPage/substeps/AddressStep.tsx" "@typescript-eslint/no-unsafe-type-assertion" 6 +"../../src/pages/AddPersonalBankAccountPage/substeps/AddressStep.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/pages/AddressPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/pages/AddressPage.tsx" "no-restricted-imports" 1 +"../../src/pages/AddressPage.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/pages/ChronosScheduleOOOPage.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/ChronosScheduleOOOPage.tsx" "no-restricted-imports" 1 +"../../src/pages/Debug/ConstantPicker.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/Debug/ConstantSelector.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/Debug/DateTimeSelector.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/Debug/DebugDetails.tsx" "@typescript-eslint/no-unsafe-type-assertion" 8 +"../../src/pages/Debug/DynamicDebugDetailsConstantPickerPage.tsx" "no-restricted-imports" 1 +"../../src/pages/Debug/DynamicDebugDetailsDateTimePickerPage.tsx" "no-restricted-imports" 1 +"../../src/pages/Debug/Report/DebugReportPage.tsx" "no-restricted-imports" 1 +"../../src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/pages/Debug/ReportAction/DebugReportActionCreatePage.tsx" "no-restricted-imports" 1 +"../../src/pages/Debug/ReportAction/DebugReportActionPage.tsx" "no-restricted-imports" 1 +"../../src/pages/Debug/ReportAction/DebugReportActionPreview.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/Debug/Transaction/DebugTransactionPage.tsx" "no-restricted-imports" 1 +"../../src/pages/Debug/Transaction/DebugTransactionViolations.tsx" "no-restricted-syntax" 1 +"../../src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 4 +"../../src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx" "no-restricted-imports" 1 +"../../src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx" "no-restricted-imports" 1 +"../../src/pages/domain/AddDomainPage.tsx" "no-restricted-imports" 1 +"../../src/pages/domain/Admins/DomainAddAdminPage.tsx" "no-restricted-imports" 1 +"../../src/pages/domain/Admins/DomainAddPrimaryContactPage.tsx" "no-restricted-imports" 1 +"../../src/pages/domain/Admins/DomainAdminsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/domain/BaseDomainMemberDetailsComponent.tsx" "no-restricted-imports" 1 +"../../src/pages/domain/BaseDomainMembersPage.tsx" "no-restricted-imports" 1 +"../../src/pages/domain/BaseDomainRequireTwoFactorAuthPage.tsx" "no-restricted-imports" 1 +"../../src/pages/domain/BaseDomainSettingsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/domain/BaseDomainVerifiedPage.tsx" "no-restricted-imports" 1 +"../../src/pages/domain/BaseVerifyDomainPage.tsx" "no-restricted-imports" 1 +"../../src/pages/domain/DomainAccessRestrictedPage.tsx" "no-restricted-imports" 1 +"../../src/pages/domain/DomainAddedPage.tsx" "no-restricted-imports" 1 +"../../src/pages/domain/DomainAlreadyExistsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/domain/DomainInitialPage.tsx" "no-restricted-imports" 1 +"../../src/pages/domain/DomainResetPage.tsx" "no-restricted-imports" 1 +"../../src/pages/domain/DomainSamlPage.tsx" "no-restricted-imports" 1 +"../../src/pages/domain/Groups/BaseDomainGroupPreferredWorkspacePage.tsx" "no-restricted-imports" 1 +"../../src/pages/domain/Groups/DomainGroupCreatePage.tsx" "no-restricted-imports" 1 +"../../src/pages/domain/Groups/DomainGroupDetailsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/domain/Groups/DomainGroupEditNamePage.tsx" "no-restricted-imports" 1 +"../../src/pages/domain/Groups/DomainGroupsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/domain/Members/DomainAddMemberPage.tsx" "no-restricted-imports" 1 +"../../src/pages/domain/Members/DomainMembersPage.tsx" "no-restricted-imports" 1 +"../../src/pages/domain/Members/MoveUserBetweenGroupsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/domain/Members/MoveUsersBetweenGroupsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/DynamicEditReportFieldPage.tsx" "no-restricted-imports" 1 +"../../src/pages/DynamicFlagCommentPage.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/pages/DynamicFlagCommentPage.tsx" "no-restricted-imports" 1 +"../../src/pages/DynamicNewReportWorkspaceSelectionPage.tsx" "no-restricted-imports" 1 +"../../src/pages/DynamicReferralDetailsPage.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/pages/DynamicReportChangeApproverPage.tsx" "no-restricted-imports" 1 +"../../src/pages/DynamicReportChangeWorkspacePage.tsx" "no-restricted-imports" 1 +"../../src/pages/DynamicReportDetailsPage/ReportDetailsContent.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 2 +"../../src/pages/DynamicReportDetailsPage/ReportDetailsMenuDebugItem.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/pages/DynamicReportDetailsPage/ReportDetailsMenuGoToRoomItem.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/pages/DynamicReportDetailsPage/ReportDetailsMenuGoToWorkspaceItem.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/pages/DynamicReportDetailsPage/ReportDetailsMenuLeaveItem.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/pages/DynamicReportDetailsPage/ReportDetailsMenuMarkAsIncompleteItem.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/pages/DynamicReportDetailsPage/ReportDetailsMenuMembersOrInviteItem.tsx" "@typescript-eslint/no-deprecated/MenuItem" 2 +"../../src/pages/DynamicReportDetailsPage/ReportDetailsMenuPrivateNotesItem.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/pages/DynamicReportDetailsPage/ReportDetailsMenuSettingsItem.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/pages/DynamicReportDetailsPage/ReportDetailsMenuTrackExpenseItems.tsx" "@typescript-eslint/no-deprecated/MenuItem" 4 +"../../src/pages/DynamicReportDetailsPage/ReportDetailsTitleSection.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/DynamicReportParticipantDetailsPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/DynamicReportParticipantDetailsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/DynamicReportParticipantRoleSelectionPage.tsx" "no-restricted-imports" 1 +"../../src/pages/DynamicReportParticipantsInvitePage.tsx" "no-restricted-imports" 1 +"../../src/pages/DynamicReportParticipantsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/DynamicRoomInvitePage.tsx" "no-restricted-imports" 1 +"../../src/pages/DynamicRoomMemberDetailsPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/DynamicRoomMemberDetailsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/DynamicRoomMembersPage.tsx" "no-restricted-imports" 1 +"../../src/pages/DynamicRoomMembersPage.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/pages/DynamicWorkspaceCurrencySelectionPage.tsx" "no-restricted-imports" 1 +"../../src/pages/EnablePayments/Pay/Activate/ActivateStep.tsx" "no-restricted-imports" 1 +"../../src/pages/EnablePayments/Pay/AdditionalDetails/AdditionalDetailsStep.tsx" "no-restricted-imports" 1 +"../../src/pages/EnablePayments/Pay/AdditionalDetails/AdditionalDetailsStep.tsx" "react-hooks/purity" 1 +"../../src/pages/EnablePayments/Pay/EnablePaymentsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/EnablePayments/Pay/Onfido/OnfidoStep.tsx" "no-restricted-imports" 1 +"../../src/pages/EnablePayments/Pay/Terms/TermsStep.tsx" "no-restricted-imports" 1 +"../../src/pages/EnablePayments/shared/IdologyQuestions.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/EnablePayments/Wallet/AddBankAccount/AddBankAccount.tsx" "no-restricted-imports" 1 +"../../src/pages/EnablePayments/Wallet/EnablePayments.tsx" "no-restricted-imports" 1 +"../../src/pages/EnablePayments/Wallet/PersonalInfo/substeps/DateOfBirthStep.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/EnablePayments/Wallet/PersonalInfo/substeps/DateOfBirthStep.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/pages/EnablePayments/Wallet/utils/getSubstepValues.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/EnablePayments/Wallet/VerifyIdentity/VerifyIdentity.tsx" "no-restricted-imports" 1 +"../../src/pages/GroupChatNameEditPage.tsx" "no-restricted-imports" 1 +"../../src/pages/home/ForYouSection/ConciergePromptBox.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/home/GettingStartedSection/hooks/useGettingStartedItems.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/home/GettingStartedSection/hooks/useGettingStartedItems.ts" "no-restricted-imports" 1 +"../../src/pages/home/TimeSensitiveSection/items/FixCompanyCardConnection.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/home/TimeSensitiveSection/items/FixCompanyCardConnection.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/home/UpcomingTravelSection/UpcomingTravelItem.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/home/UpcomingTravelSection/useUpcomingTravelReservations.ts" "react-hooks/purity" 1 +"../../src/pages/home/YourSpendSection/CardRow.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/home/YourSpendSection/SpendSummaryRow.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/home/YourSpendSection/useYourSpendData.ts" "no-restricted-imports" 1 +"../../src/pages/inbox/cleanStaleReportActionBackToParam.ts" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/pages/inbox/conciergeDraftState.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/inbox/hooks/useReportWasDeleted.ts" "react-hooks/set-state-in-effect" 1 +"../../src/pages/inbox/ReactionListWrapper.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/inbox/report/actionContents/ActionContentRouter.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/inbox/report/actionContents/JoinRequestContent.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/inbox/report/actionContents/ReimbursementDeQueuedContent.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/inbox/report/comment/TextCommentFragment.tsx" "@typescript-eslint/no-deprecated/fontSize" 1 +"../../src/pages/inbox/report/ContextMenu/BaseReportActionContextMenu.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/inbox/report/ContextMenu/BaseReportActionContextMenu.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/pages/inbox/report/ContextMenu/BaseReportActionContextMenu.tsx" "react-hooks/refs" 1 +"../../src/pages/inbox/report/ContextMenu/ContextMenuActions.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/inbox/report/ContextMenu/PopoverReportActionContextMenu.tsx" "@typescript-eslint/no-deprecated/ConfirmModal" 1 +"../../src/pages/inbox/report/ContextMenu/PopoverReportActionContextMenu.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/inbox/report/ContextMenu/PopoverReportActionContextMenu.tsx" "react-hooks/refs" 25 +"../../src/pages/inbox/report/DynamicReportDetailsExportPage.tsx" "no-restricted-imports" 1 +"../../src/pages/inbox/report/LinkPreviewer.tsx" "@typescript-eslint/no-deprecated/fontSize" 3 +"../../src/pages/inbox/report/ReactionList/HeaderReactionList.tsx" "no-restricted-syntax" 1 +"../../src/pages/inbox/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/inbox/report/ReportActionCompose/ComposerWithSuggestions.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/pages/inbox/report/ReportActionCompose/ComposerWithSuggestions.tsx" "react-hooks/refs" 7 +"../../src/pages/inbox/report/ReportActionCompose/SuggestionEmoji.tsx" "react-hooks/refs" 1 +"../../src/pages/inbox/report/ReportActionCompose/SuggestionMention.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/inbox/report/ReportActionCompose/SuggestionMention.tsx" "react-hooks/refs" 3 +"../../src/pages/inbox/report/ReportActionCompose/SuggestionMention.tsx" "unicorn/prefer-at" 2 +"../../src/pages/inbox/report/ReportActionCompose/useAttachmentPicker.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/inbox/report/ReportActionCompose/useComposerFocus.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/inbox/report/ReportActionCompose/useReceiptDrop.ts" "@typescript-eslint/no-unsafe-type-assertion" 5 +"../../src/pages/inbox/report/ReportActionItem.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/inbox/report/ReportActionItem.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/pages/inbox/report/ReportActionItem.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/pages/inbox/report/ReportActionItemMessageEdit.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/inbox/report/ReportActionItemMessageEdit.tsx" "no-restricted-syntax" 1 +"../../src/pages/inbox/report/ReportActionItemMessageEdit.tsx" "react-hooks/refs" 5 +"../../src/pages/inbox/report/ReportActionItemMessageEdit.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/pages/inbox/report/ReportActionsListItemRenderer.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/inbox/report/SearchActionHeader.tsx" "@typescript-eslint/no-deprecated/fontSize" 1 +"../../src/pages/inbox/report/shouldUseEmojiPickerSelection/index.web.ts" "no-restricted-syntax" 1 +"../../src/pages/inbox/report/TripSummary.tsx" "rulesdir/no-default-id-values" 1 +"../../src/pages/inbox/report/useDebouncedSaveDraft.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/inbox/report/useReportUnreadMessageScrollTracking.ts" "react-hooks/preserve-manual-memoization" 1 +"../../src/pages/inbox/report/WhisperBanner.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/inbox/report/withReportAndPrivateNotesOrNotFound.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/inbox/report/withReportAndPrivateNotesOrNotFound.tsx" "no-restricted-imports" 1 +"../../src/pages/inbox/report/withReportAndReportActionOrNotFound.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/inbox/report/withReportOrNotFound.tsx" "react-hooks/refs" 3 +"../../src/pages/inbox/ReportHeader.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/inbox/ReportNavigateAwayHandler.tsx" "react-hooks/exhaustive-deps" 1 +"../../src/pages/inbox/sidebar/FABPopoverContent/FABFocusableMenuItem.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/inbox/sidebar/FABPopoverContent/menuItems/InvoiceMenuItem.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/inbox/sidebar/FABPopoverContent/menuItems/NewWorkspaceMenuItem.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/inbox/sidebar/FABPopoverContent/menuItems/NewWorkspaceMenuItem.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/inbox/sidebar/FABPopoverContent/menuItems/TravelMenuItem.tsx" "no-restricted-imports" 1 +"../../src/pages/inbox/sidebar/FABPopoverContent/useScanActions.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/iou/DynamicSplitBillDetailsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/iou/DynamicSplitExpenseCreateDateRangePage.tsx" "no-restricted-imports" 1 +"../../src/pages/iou/DynamicSplitExpenseEditPage.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/iou/DynamicSplitExpenseEditPage.tsx" "no-restricted-imports" 1 +"../../src/pages/iou/DynamicSplitExpensePage.tsx" "@typescript-eslint/no-deprecated/MenuItem" 2 +"../../src/pages/iou/DynamicSplitExpensePage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/iou/DynamicSplitExpensePage.tsx" "no-restricted-imports" 1 +"../../src/pages/iou/DynamicSplitExpensePage.tsx" "react-hooks/set-state-in-effect" 3 +"../../src/pages/iou/HoldReasonFormView.tsx" "no-restricted-imports" 1 +"../../src/pages/iou/MoneyRequestAmountForm.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/pages/iou/RejectReasonFormView.tsx" "no-restricted-imports" 1 +"../../src/pages/iou/request/DistanceRequestStartPage.tsx" "no-restricted-imports" 1 +"../../src/pages/iou/request/IOURequestStartPage.tsx" "no-restricted-imports" 1 +"../../src/pages/iou/request/ParticipantSearchResults.tsx" "@typescript-eslint/no-unsafe-type-assertion" 4 +"../../src/pages/iou/request/step/DistanceManualTabContent.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/iou/request/step/DynamicIOURequestEditReport.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/iou/request/step/DynamicIOURequestStepDistance.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 3 +"../../src/pages/iou/request/step/DynamicIOURequestStepDistance.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/pages/iou/request/step/DynamicIOURequestStepReport.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/iou/request/step/DynamicIOURequestStepSubrate.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/iou/request/step/DynamicIOURequestStepSubrate.tsx" "no-restricted-imports" 1 +"../../src/pages/iou/request/step/DynamicIOURequestStepSubrate.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/pages/iou/request/step/DynamicIOURequestStepUpgrade.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/iou/request/step/DynamicIOURequestStepUpgrade.tsx" "no-restricted-imports" 1 +"../../src/pages/iou/request/step/DynamicIOURequestStepWaypoint.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/iou/request/step/DynamicIOURequestStepWaypoint.tsx" "no-restricted-imports" 1 +"../../src/pages/iou/request/step/IOURequestEditReportCommon.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/pages/iou/request/step/IOURequestEditReportCommon.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/iou/request/step/IOURequestStepAmount.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/iou/request/step/IOURequestStepAmount.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/pages/iou/request/step/IOURequestStepConfirmation/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/pages/iou/request/step/IOURequestStepConfirmation/index.tsx" "no-restricted-imports" 1 +"../../src/pages/iou/request/step/IOURequestStepConfirmation/index.tsx" "react-hooks/preserve-manual-memoization" 2 +"../../src/pages/iou/request/step/IOURequestStepCurrencyModal.tsx" "no-restricted-imports" 1 +"../../src/pages/iou/request/step/IOURequestStepDistanceGPS/Waypoints/index.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 2 +"../../src/pages/iou/request/step/IOURequestStepDistanceMap.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 3 +"../../src/pages/iou/request/step/IOURequestStepDistanceMap.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/pages/iou/request/step/IOURequestStepGPSTripEdit/Slider.tsx" "@typescript-eslint/no-deprecated/Gesture.Pan" 2 +"../../src/pages/iou/request/step/IOURequestStepOdometerImage/index.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../src/pages/iou/request/step/IOURequestStepOdometerImage/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/iou/request/step/IOURequestStepScan/components/NavigationAwareCamera/WebCamera.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/iou/request/step/IOURequestStepScan/components/ScanEditReceipt.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/iou/request/step/IOURequestStepScan/DynamicReceiptView/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/iou/request/step/IOURequestStepScan/DynamicReceiptView/index.tsx" "no-restricted-imports" 1 +"../../src/pages/iou/request/step/IOURequestStepScan/DynamicReceiptView/index.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/pages/iou/request/step/IOURequestStepScan/hooks/useScanRouteParams.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/iou/request/step/IOURequestStepScan/utils/buildReceiptFiles.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/iou/request/step/IOURequestStepScan/utils/getFileSource.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/iou/request/step/StepScreenDragAndDropWrapper.tsx" "no-restricted-imports" 1 +"../../src/pages/iou/request/step/StepScreenWrapper.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/iou/request/step/StepScreenWrapper.tsx" "no-restricted-imports" 1 +"../../src/pages/iou/request/step/withFullTransactionOrNotFound.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/iou/request/step/withWritableReportOrNotFound.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/LoadingPage.tsx" "no-restricted-imports" 1 +"../../src/pages/LogInWithShortLivedAuthTokenPage.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/LogOutPreviousUserPage.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/media/AttachmentModalScreen/AttachmentModalBaseContent/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/pages/media/AttachmentModalScreen/AttachmentModalBaseContent/index.tsx" "no-restricted-imports" 1 +"../../src/pages/media/AttachmentModalScreen/AttachmentModalBaseContent/index.tsx" "react-hooks/set-state-in-effect" 3 +"../../src/pages/media/AttachmentModalScreen/AttachmentModalContext.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/media/AttachmentModalScreen/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 16 +"../../src/pages/media/AttachmentModalScreen/routes/hooks/useReportAttachmentModalType.ts" "react-hooks/set-state-in-effect" 1 +"../../src/pages/media/AttachmentModalScreen/routes/TransactionReceiptModalContent.tsx" "@typescript-eslint/no-unsafe-type-assertion" 7 +"../../src/pages/media/AttachmentModalScreen/routes/TransactionReceiptModalContent.tsx" "react-hooks/set-state-in-effect" 2 +"../../src/pages/MissingPersonalDetails/MissingPersonalDetailsContent.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/MissingPersonalDetails/MissingPersonalDetailsContent.tsx" "no-restricted-imports" 1 +"../../src/pages/MissingPersonalDetails/MissingPersonalDetailsValidateCodePage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/MissingPersonalDetails/subPages/Address.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../src/pages/MissingPersonalDetails/subPages/Address.tsx" "@typescript-eslint/no-unsafe-type-assertion" 5 +"../../src/pages/MissingPersonalDetails/subPages/Address.tsx" "react-hooks/refs" 4 +"../../src/pages/MultifactorAuthentication/AuthorizeTransactionPage/AuthorizeCardTransactionPreview.tsx" "@typescript-eslint/no-deprecated/fontSize" 2 +"../../src/pages/MultifactorAuthentication/AuthorizeTransactionPage/index.tsx" "no-restricted-imports" 1 +"../../src/pages/MultifactorAuthentication/PromptPage.tsx" "no-restricted-imports" 1 +"../../src/pages/MultifactorAuthentication/RevokePage.tsx" "no-restricted-imports" 1 +"../../src/pages/MultifactorAuthentication/ValidateCodePage.tsx" "no-restricted-imports" 1 +"../../src/pages/NewChatConfirmPage.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/NewChatConfirmPage.tsx" "no-restricted-imports" 1 +"../../src/pages/NewChatSelectorPage.tsx" "no-restricted-imports" 1 +"../../src/pages/OnboardingInterestedFeatures/BaseOnboardingInterestedFeatures.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/OnboardingPersonalTrackGoal/BaseOnboardingPersonalTrackGoal.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/pages/OnboardingPrivateDomain/BaseOnboardingPrivateDomain.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/PrivateNotes/PrivateNotesEditPage.tsx" "no-restricted-imports" 1 +"../../src/pages/PrivateNotes/PrivateNotesListPage.tsx" "no-restricted-imports" 1 +"../../src/pages/ProfilePage.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/pages/ProfilePage.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 5 +"../../src/pages/ProfilePage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/ProfilePage.tsx" "no-restricted-imports" 1 +"../../src/pages/ReimbursementAccount/AddressFormFields.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/ReimbursementAccount/ConnectedVerifiedBankAccount.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/ReimbursementAccount/ConnectedVerifiedBankAccount.tsx" "no-restricted-imports" 1 +"../../src/pages/ReimbursementAccount/EnterSignerInfo/subSteps/Address.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/ReimbursementAccount/NonUSD/BankInfo/BankInfo.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/ReimbursementAccount/NonUSD/BankInfo/subSteps/AccountHolderDetails.tsx" "@typescript-eslint/no-unsafe-type-assertion" 7 +"../../src/pages/ReimbursementAccount/NonUSD/BankInfo/subSteps/BankAccountDetails.tsx" "@typescript-eslint/no-unsafe-type-assertion" 7 +"../../src/pages/ReimbursementAccount/NonUSD/BankInfo/subSteps/Confirmation.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/ReimbursementAccount/NonUSD/BeneficialOwnerInfo/BeneficialOwnerDetailsFormSubSteps/Address.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/ReimbursementAccount/NonUSD/BeneficialOwnerInfo/BeneficialOwnerDetailsFormSubSteps/Confirmation.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/ReimbursementAccount/NonUSD/BeneficialOwnerInfo/BeneficialOwnerDetailsFormSubSteps/Nationality.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/ReimbursementAccount/NonUSD/BusinessInfo/subSteps/Confirmation.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/pages/ReimbursementAccount/NonUSD/BusinessInfo/subSteps/ContactInformation.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/ReimbursementAccount/NonUSD/BusinessInfo/subSteps/IncorporationLocation.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/ReimbursementAccount/NonUSD/BusinessInfo/subSteps/Website.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/ReimbursementAccount/NonUSD/Finish/index.tsx" "no-restricted-imports" 1 +"../../src/pages/ReimbursementAccount/NonUSD/SignerInfo/HangTight.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/ReimbursementAccount/NonUSD/SignerInfo/subSteps/Address.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/ReimbursementAccount/NonUSD/utils/getBankInfoStepValues.ts" "@typescript-eslint/no-unsafe-type-assertion" 11 +"../../src/pages/ReimbursementAccount/NonUSD/utils/getInitialSubStepForBankInfoStep.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/ReimbursementAccount/NonUSD/utils/getInitialSubStepForBusinessInfoStep.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/ReimbursementAccount/NonUSD/utils/getInputKeysForBankInfoStep.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/ReimbursementAccount/NonUSD/utils/getValuesForBeneficialOwner.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/ReimbursementAccount/NonUSD/utils/getValuesForSignerInfo.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/ReimbursementAccount/ReimbursementAccountPage.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../src/pages/ReimbursementAccount/ReimbursementAccountPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/ReimbursementAccount/ReimbursementAccountPage.tsx" "no-restricted-imports" 1 +"../../src/pages/ReimbursementAccount/ReimbursementAccountPage.tsx" "react-hooks/refs" 3 +"../../src/pages/ReimbursementAccount/ReimbursementAccountPage.tsx" "react-hooks/set-state-in-effect" 4 +"../../src/pages/ReimbursementAccount/USD/BankInfo/BankInfo.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/ReimbursementAccount/USD/BeneficialOwnerInfo/BeneficialOwnersStep.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/ReimbursementAccount/USD/BeneficialOwnerInfo/subSteps/BeneficialOwnerDetailsFormSubSteps/LegalNameUBO.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/ReimbursementAccount/USD/BusinessInfo/subSteps/ConfirmationBusiness.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/ReimbursementAccount/USD/BusinessInfo/subSteps/IncorporationDateBusiness.tsx" "react-hooks/purity" 1 +"../../src/pages/ReimbursementAccount/USD/BusinessInfo/subSteps/IndustryCode/IndustryCodeSelector.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/pages/ReimbursementAccount/USD/BusinessInfo/subSteps/TypeBusiness/BusinessTypePicker/BusinessTypeSelectorModal.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/ReimbursementAccount/USD/BusinessInfo/subSteps/TypeBusiness/BusinessTypePicker/BusinessTypeSelectorModal.tsx" "no-restricted-imports" 1 +"../../src/pages/ReimbursementAccount/USD/BusinessInfo/subSteps/TypeBusiness/BusinessTypePicker/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/ReimbursementAccount/USD/BusinessInfo/subSteps/WebsiteBusiness.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/ReimbursementAccount/USD/ConnectBankAccount/components/BankAccountValidationForm.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/ReimbursementAccount/USD/ConnectBankAccount/components/BankAccountValidationForm.tsx" "unicorn/prefer-string-replace-all" 1 +"../../src/pages/ReimbursementAccount/USD/ConnectBankAccount/components/Enable2FACard.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/ReimbursementAccount/USD/ConnectBankAccount/ConnectBankAccount.tsx" "no-restricted-imports" 1 +"../../src/pages/ReimbursementAccount/utils/getSubStepValues.ts" "@typescript-eslint/no-unsafe-type-assertion" 4 +"../../src/pages/ReimbursementAccount/VerifiedBankAccountFlowEntryPoint.tsx" "no-restricted-imports" 1 +"../../src/pages/RejectExpenseReportPage.tsx" "no-restricted-imports" 1 +"../../src/pages/ReportDescriptionPage.tsx" "no-restricted-imports" 1 +"../../src/pages/ReportDescriptionPage.tsx" "no-restricted-syntax" 1 +"../../src/pages/RestrictedAction/Workspace/WorkspaceAdminRestrictedAction.tsx" "no-restricted-imports" 1 +"../../src/pages/RestrictedAction/Workspace/WorkspaceOwnerRestrictedAction/index.native.tsx" "no-restricted-imports" 1 +"../../src/pages/RestrictedAction/Workspace/WorkspaceOwnerRestrictedAction/index.tsx" "no-restricted-imports" 1 +"../../src/pages/RestrictedAction/Workspace/WorkspaceUserRestrictedAction.tsx" "no-restricted-imports" 1 +"../../src/pages/RoomDescriptionPage.tsx" "no-restricted-imports" 1 +"../../src/pages/ScheduleCall/ScheduleCallConfirmationPage.tsx" "no-restricted-imports" 1 +"../../src/pages/ScheduleCall/ScheduleCallPage.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/ScheduleCall/ScheduleCallPage.tsx" "no-restricted-imports" 1 +"../../src/pages/Search/SavedSearchRenamePage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/Search/SavedSearchRenamePage.tsx" "no-restricted-imports" 1 +"../../src/pages/Search/SearchAdvancedFiltersContentPage/SearchAdvancedFiltersContentBase.tsx" "no-restricted-imports" 1 +"../../src/pages/Search/SearchAdvancedFiltersPage/SearchAdvancedFiltersBase.tsx" "no-restricted-imports" 1 +"../../src/pages/Search/SearchChangeApproverPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/Search/SearchChangeApproverPage.tsx" "no-restricted-imports" 1 +"../../src/pages/Search/SearchEditMultiple/SearchEditMultipleAmountPage.tsx" "no-restricted-imports" 1 +"../../src/pages/Search/SearchEditMultiple/SearchEditMultipleAttendeesPage.tsx" "no-restricted-imports" 1 +"../../src/pages/Search/SearchEditMultiple/SearchEditMultipleBooleanPage.tsx" "no-restricted-imports" 1 +"../../src/pages/Search/SearchEditMultiple/SearchEditMultipleCategoryPage.tsx" "no-restricted-imports" 1 +"../../src/pages/Search/SearchEditMultiple/SearchEditMultipleDatePage.tsx" "no-restricted-imports" 1 +"../../src/pages/Search/SearchEditMultiple/SearchEditMultipleDescriptionPage.tsx" "no-restricted-imports" 1 +"../../src/pages/Search/SearchEditMultiple/SearchEditMultipleMerchantPage.tsx" "no-restricted-imports" 1 +"../../src/pages/Search/SearchEditMultiple/SearchEditMultiplePage.tsx" "no-restricted-imports" 1 +"../../src/pages/Search/SearchEditMultiple/SearchEditMultipleTagPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/Search/SearchEditMultiple/SearchEditMultipleTagPage.tsx" "no-restricted-imports" 1 +"../../src/pages/Search/SearchEditMultiple/SearchEditMultipleTaxPage.tsx" "no-restricted-imports" 1 +"../../src/pages/Search/SearchEditMultiple/SearchEditMultipleUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/pages/Search/SearchMoneyRequestReportPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/Search/SearchPage.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/pages/Search/SearchPageNarrow/index.tsx" "no-restricted-imports" 1 +"../../src/pages/Search/SearchSavePage.tsx" "no-restricted-imports" 1 +"../../src/pages/Search/SearchTransactionsChangeReport.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/SetDefaultWorkspacePage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/AboutPage/AboutPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/AboutPage/AboutPage.tsx" "react-hooks/preserve-manual-memoization" 9 +"../../src/pages/settings/Agents/AgentsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Agents/DynamicAddAgentPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Agents/DynamicEditAgentPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Agents/DynamicNewAgentPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Agents/Fields/DynamicEditAgentAvatarPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Agents/Fields/DynamicEditNamePage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Agents/Fields/DynamicEditPromptPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/settings/Agents/Fields/DynamicEditPromptPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/AppDownloadLinks.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Copilot/CopilotPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/settings/Copilot/CopilotPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/DynamicKeyboardShortcutsPage.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/pages/settings/DynamicKeyboardShortcutsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/ExitSurvey/DynamicExitSurveyConfirmPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/settings/ExitSurvey/DynamicExitSurveyConfirmPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/ExitSurvey/DynamicExitSurveyReasonPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/HelpPage/HelpPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Preferences/LanguagePage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Preferences/PaymentCurrencyPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Preferences/PreferencesPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Preferences/PriorityModePage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Preferences/ThemePage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Profile/AgentAIPromptSection.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/settings/Profile/Avatar/UserProfileAvatar.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Profile/Contacts/DynamicContactMethodDetailsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Profile/Contacts/DynamicContactMethodDetailsPage.tsx" "react-hooks/refs" 3 +"../../src/pages/settings/Profile/Contacts/DynamicContactMethodDetailsPage.tsx" "react-hooks/set-state-in-effect" 3 +"../../src/pages/settings/Profile/Contacts/DynamicContactMethodsPage.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/pages/settings/Profile/Contacts/DynamicContactMethodsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Profile/Contacts/DynamicNewContactMethodPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Profile/Contacts/DynamicNewContactMethodPage.tsx" "react-hooks/preserve-manual-memoization" 1 +"../../src/pages/settings/Profile/CustomStatus/SetDatePage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Profile/CustomStatus/SetDatePage.tsx" "react-hooks/purity" 1 +"../../src/pages/settings/Profile/CustomStatus/SetTimePage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Profile/CustomStatus/StatusClearAfterPage.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 2 +"../../src/pages/settings/Profile/CustomStatus/StatusClearAfterPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Profile/CustomStatus/StatusClearAfterPage.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/pages/settings/Profile/CustomStatus/StatusPage.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/settings/Profile/CustomStatus/StatusPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Profile/CustomStatus/StatusPage.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/pages/settings/Profile/CustomStatus/VacationDelegateMissingWorkspacesPage/index.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Profile/DisplayNamePage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Profile/PersonalDetails/BaseLegalNamePage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Profile/PersonalDetails/DynamicCountrySelectionPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/settings/Profile/PersonalDetails/DynamicCountrySelectionPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Profile/PersonalDetails/DynamicStateSelectionPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/settings/Profile/PersonalDetails/DynamicStateSelectionPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Profile/PersonalDetails/PersonalAddressPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/settings/Profile/PersonalDetails/PrivatePersonalDetailsConfirmValidateCodePage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/settings/Profile/PersonalDetails/PrivatePersonalDetailsPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 8 +"../../src/pages/settings/Profile/PersonalDetails/PrivatePersonalDetailsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Profile/PersonalDetails/PrivatePersonalDetailsPage.tsx" "react-hooks/purity" 2 +"../../src/pages/settings/Profile/ProfilePage.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/settings/Profile/ProfilePage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/settings/Profile/ProfilePage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Profile/PronounsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Profile/PronounsPage.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/pages/settings/Profile/TimezoneInitialPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/settings/Profile/TimezoneInitialPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Profile/TimezoneSelectPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/settings/Profile/TimezoneSelectPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Report/DynamicNotificationPreferencePage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Report/DynamicReportSettingsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Report/DynamicVisibilityPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Report/DynamicWriteCapabilityPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Report/RoomNamePage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Rules/ExpenseRulesPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Rules/RulePageBase.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Security/AddDelegate/AddDelegatePage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Security/AddDelegate/ConfirmDelegatePage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/settings/Security/AddDelegate/ConfirmDelegateValidateCodePage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/settings/Security/AddDelegate/SelectDelegateRolePage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Security/AddDelegate/UpdateDelegateRole/UpdateDelegateRolePage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Security/AddDelegate/UpdateDelegateRole/UpdateDelegateValidateCodePage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/settings/Security/CloseAccount/CloseAccountPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Security/DeviceManagementPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Security/LockAccount/FailedToLockAccountPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Security/LockAccount/UnlockAccountPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Security/MergeAccounts/AccountDetailsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Security/MergeAccounts/AccountValidatePage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Security/MergeAccounts/MergeResultPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Security/SecuritySettingsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Security/TwoFactorAuth/EnabledPage.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/pages/settings/Security/TwoFactorAuth/EnabledPage.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthWrapper.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/SettingsMenuItem.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/pages/settings/Subscription/CancelSubscriptionPage/index.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Subscription/CardSection/BillingBanner/TrialEndedBillingBanner.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/settings/Subscription/DisableAutoRenewSurveyPage/index.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Subscription/ExpensifyCodePage/index.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Subscription/PaymentCard/ChangeBillingCurrency/index.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/settings/Subscription/PaymentCard/ChangeBillingCurrency/index.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Subscription/PaymentCard/ChangeBillingCurrency/index.tsx" "no-restricted-syntax" 1 +"../../src/pages/settings/Subscription/PaymentCard/DynamicPaymentCardCurrencySelectorPage.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/settings/Subscription/PaymentCard/DynamicPaymentCardCurrencySelectorPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/settings/Subscription/PaymentCard/DynamicPaymentCardCurrencySelectorPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Subscription/PaymentCard/index.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/settings/Subscription/PaymentCard/index.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Subscription/SubscriptionPlan/ComparePlansModal.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Subscription/SubscriptionPlan/ComparePlansModal.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/pages/settings/Subscription/SubscriptionPlan/DynamicSubscriptionPlanDowngradeBlockedPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Subscription/SubscriptionPlan/SubscriptionPlanCardActionButton.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/settings/Subscription/SubscriptionSettings/index.native.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Subscription/SubscriptionSettings/index.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Subscription/SubscriptionSettingsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Subscription/SubscriptionSize/index.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Troubleshoot/DynamicBetaOverridesPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Troubleshoot/ServerSelector.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Troubleshoot/TroubleshootPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Troubleshoot/TroubleshootPage.tsx" "react-hooks/purity" 1 +"../../src/pages/settings/useInitialSettingsPageMenuData.ts" "react-hooks/purity" 1 +"../../src/pages/settings/VerifyAccountPageBase.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Wallet/AddCardToDigitalWalletPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Wallet/AddDebitCardPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Wallet/BankAccountPurposePage/index.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Wallet/BankAccountPurposePage/substeps/BankAccountPurpose.tsx" "@typescript-eslint/no-deprecated/MenuItem" 2 +"../../src/pages/settings/Wallet/BankAccountPurposePage/substeps/CountrySelection.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/settings/Wallet/CardAddedToWalletPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Wallet/ChooseTransferAccountPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/settings/Wallet/ChooseTransferAccountPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Wallet/CountrySelectionList.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/settings/Wallet/EnableGlobalReimbursements/EnableGlobalReimbursementsAgreementsPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/settings/Wallet/EnableGlobalReimbursements/EnableGlobalReimbursementsBusinessPage/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/settings/Wallet/ExpensifyCardPage/ChangePINPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Wallet/ExpensifyCardPage/index.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 2 +"../../src/pages/settings/Wallet/ExpensifyCardPage/index.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Wallet/ImportTransactionsCardNamePage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Wallet/ImportTransactionsCurrencyPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Wallet/ImportTransactionsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Wallet/InternationalDepositAccount/InternationalDepositAccountContent.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Wallet/InternationalDepositAccount/subPages/AccountFlowEntryPoint.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Wallet/InternationalDepositAccount/subPages/Confirmation.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/settings/Wallet/InternationalDepositAccount/utils.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/settings/Wallet/InternationalDepositAccount/utils.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/settings/Wallet/PaymentMethodList.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/pages/settings/Wallet/PaymentMethodList.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 3 +"../../src/pages/settings/Wallet/PaymentMethodList.tsx" "@typescript-eslint/no-unsafe-type-assertion" 7 +"../../src/pages/settings/Wallet/PaymentMethodListItem.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/pages/settings/Wallet/PersonalCardDetailsHeaderMenu.tsx" "@typescript-eslint/no-deprecated/MenuItem" 2 +"../../src/pages/settings/Wallet/PersonalCardDetailsPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/settings/Wallet/PersonalCardDetailsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Wallet/PersonalCardEditNamePage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Wallet/PersonalCardEditTransactionStartDatePage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Wallet/PersonalCards/FixPersonalCardConnectionPage/index.native.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Wallet/PersonalCards/FixPersonalCardConnectionPage/index.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Wallet/PersonalCards/FixPersonalCardConnectionPage/useFixPersonalCardConnection.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../src/pages/settings/Wallet/PersonalCards/FixPersonalCardConnectionPage/useFixPersonalCardConnection.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/settings/Wallet/PersonalCards/steps/BankConnection/index.native.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Wallet/PersonalCards/steps/BankConnection/index.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Wallet/PersonalCards/steps/PlaidConnectionStep.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Wallet/PersonalCards/steps/SelectBankStep.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Wallet/PersonalCards/steps/SelectCountryStep.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/settings/Wallet/PersonalCards/steps/SelectCountryStep.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Wallet/PersonalCards/steps/SelectCountryStep.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/pages/settings/Wallet/PersonalCards/steps/SuccessStep.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Wallet/PersonalCards/upgrade/PersonalCardUpgradePage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Wallet/ReportCardLostConfirmValidateCodePage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Wallet/ReportCardLostPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Wallet/ReportVirtualCardFraudConfirmationPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Wallet/ReportVirtualCardFraudPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Wallet/ShareBankAccount/ShareBankAccount.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Wallet/ShareBankAccount/ShareBankAccount.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/pages/settings/Wallet/TransactionsImportedPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Wallet/TransferBalancePage.tsx" "@typescript-eslint/no-deprecated/MenuItem" 2 +"../../src/pages/settings/Wallet/TransferBalancePage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Wallet/TravelCVVPage/TravelCVVPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Wallet/UnshareBankAccount/UnshareBankAccount.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Wallet/UpdatePersonalBankAccountPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/settings/Wallet/UpdatePersonalBankAccountPage.tsx" "no-restricted-imports" 1 +"../../src/pages/settings/Wallet/WalletPage/CardDetails.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 4 +"../../src/pages/settings/Wallet/WalletPage/index.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/settings/Wallet/WalletPage/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/settings/Wallet/WalletPage/index.tsx" "no-restricted-imports" 2 +"../../src/pages/settings/Wallet/WalletPage/index.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/pages/Share/ShareDetailsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/Share/ShareRootPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/Share/ShareRootPage.tsx" "no-restricted-imports" 1 +"../../src/pages/Share/ShareTab.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/Share/SubmitDetailsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/ShareCodePage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/ShareCodePage.tsx" "no-restricted-imports" 1 +"../../src/pages/signin/SAMLSignInPage/index.native.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/signin/SAMLSignInPage/index.native.tsx" "no-restricted-imports" 1 +"../../src/pages/signin/SignInModal.tsx" "no-restricted-imports" 1 +"../../src/pages/signin/SignInPage.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/pages/signin/SignInPageLayout/BackgroundImage/index.native.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/signin/SignInPageLayout/FooterRow/index.native.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/signin/SignInPageLayout/SignInHeroCopy.tsx" "rulesdir/no-raw-typography" 2 +"../../src/pages/signin/SignInPageLayout/SignInPageContent.tsx" "rulesdir/no-raw-typography" 2 +"../../src/pages/signin/SMSDeliveryFailurePage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.tsx" "react-hooks/set-state-in-effect" 4 +"../../src/pages/tasks/DynamicNewTaskDescriptionPage.tsx" "no-restricted-imports" 1 +"../../src/pages/tasks/DynamicNewTaskDetailsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/tasks/DynamicNewTaskPage.tsx" "no-restricted-imports" 1 +"../../src/pages/tasks/DynamicNewTaskTitlePage.tsx" "no-restricted-imports" 1 +"../../src/pages/tasks/DynamicTaskAssigneeSelectorModal.tsx" "no-restricted-imports" 1 +"../../src/pages/tasks/DynamicTaskShareDestinationSelectorModal.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/tasks/DynamicTaskShareDestinationSelectorModal.tsx" "no-restricted-imports" 1 +"../../src/pages/tasks/TaskDescriptionPage.tsx" "no-restricted-imports" 1 +"../../src/pages/tasks/TaskTitlePage.tsx" "no-restricted-imports" 1 +"../../src/pages/TeachersUnite/ImTeacherUpdateEmailPage.tsx" "no-restricted-imports" 1 +"../../src/pages/TeachersUnite/IntroSchoolPrincipalPage.tsx" "no-restricted-imports" 1 +"../../src/pages/TeachersUnite/KnowATeacherPage.tsx" "no-restricted-imports" 1 +"../../src/pages/TeachersUnite/SaveTheWorldPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/TeachersUnite/SaveTheWorldPage.tsx" "no-restricted-imports" 1 +"../../src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/TransactionDuplicate/DuplicateTransactionItem.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/TransactionDuplicate/DynamicConfirmationPage.tsx" "@typescript-eslint/no-deprecated/fontSize" 1 +"../../src/pages/TransactionDuplicate/DynamicConfirmationPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/TransactionDuplicate/DynamicConfirmationPage.tsx" "no-restricted-imports" 2 +"../../src/pages/TransactionDuplicate/DynamicConfirmationPage.tsx" "react-hooks/refs" 7 +"../../src/pages/TransactionDuplicate/DynamicReviewBillablePage.tsx" "no-restricted-imports" 1 +"../../src/pages/TransactionDuplicate/DynamicReviewCategoryPage.tsx" "no-restricted-imports" 1 +"../../src/pages/TransactionDuplicate/DynamicReviewDescriptionPage.tsx" "no-restricted-imports" 1 +"../../src/pages/TransactionDuplicate/DynamicReviewMerchantPage.tsx" "no-restricted-imports" 1 +"../../src/pages/TransactionDuplicate/DynamicReviewPage.tsx" "no-restricted-imports" 1 +"../../src/pages/TransactionDuplicate/DynamicReviewReimbursablePage.tsx" "no-restricted-imports" 1 +"../../src/pages/TransactionDuplicate/DynamicReviewTagPage.tsx" "no-restricted-imports" 1 +"../../src/pages/TransactionDuplicate/DynamicReviewTaxCodePage.tsx" "no-restricted-imports" 1 +"../../src/pages/TransactionDuplicate/ReviewFields.tsx" "@typescript-eslint/no-deprecated/fontSize" 1 +"../../src/pages/TransactionMerge/DynamicConfirmationPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/TransactionMerge/DynamicConfirmationPage.tsx" "no-restricted-imports" 1 +"../../src/pages/TransactionMerge/DynamicDetailsReviewPage.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/TransactionMerge/DynamicDetailsReviewPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/TransactionMerge/DynamicDetailsReviewPage.tsx" "no-restricted-imports" 1 +"../../src/pages/TransactionMerge/DynamicMergeTransactionsListPage.tsx" "no-restricted-imports" 1 +"../../src/pages/TransactionMerge/DynamicReceiptReviewPage.tsx" "no-restricted-imports" 1 +"../../src/pages/TransactionMerge/MergeTransactionItem.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/TransactionMerge/MergeTransactionsListContent.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/TransactionMerge/MergeTransactionsListContent.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/Travel/CarTripDetails.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 2 +"../../src/pages/Travel/DynamicDomainPermissionInfoPage.tsx" "no-restricted-imports" 1 +"../../src/pages/Travel/DynamicPublicDomainErrorPage.tsx" "no-restricted-imports" 1 +"../../src/pages/Travel/DynamicTravelUpgrade.tsx" "no-restricted-imports" 2 +"../../src/pages/Travel/DynamicTripDetailsPage.tsx" "@typescript-eslint/no-deprecated/MenuItem" 2 +"../../src/pages/Travel/DynamicTripSummaryPage.tsx" "no-restricted-imports" 1 +"../../src/pages/Travel/DynamicWorkspaceConfirmationForTravelPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/Travel/EnableTravel/EnableTravelContent.tsx" "no-restricted-imports" 1 +"../../src/pages/Travel/HotelTripDetails.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 3 +"../../src/pages/Travel/MyTripsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/Travel/TravelDotLinkWebview.tsx" "import/no-named-as-default" 1 +"../../src/pages/Travel/TravelDotLinkWebview.tsx" "no-restricted-imports" 1 +"../../src/pages/TripChatNameEditPage.tsx" "no-restricted-imports" 1 +"../../src/pages/UnreportedExpenseListItem.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/ValidateLoginPage/index.web.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/pages/wallet/WalletStatementPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/wallet/WalletStatementPage.tsx" "react-hooks/purity" 2 +"../../src/pages/workspace/accounting/AccountingContext/default.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/accounting/AccountingSetupWebViewPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/accounting/businessCentral/BusinessCentralPrerequisitesPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/accounting/campfire/advanced/CampfireAdvancedPage.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 5 +"../../src/pages/workspace/accounting/campfire/CampfireExistingConnectionsPage.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/pages/workspace/accounting/campfire/CampfireExistingConnectionsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/accounting/campfire/export/CampfireExportPage.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 6 +"../../src/pages/workspace/accounting/campfire/export/CampfireExportPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/accounting/certinia/advanced/CertiniaAdvancedPage.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/workspace/accounting/certinia/CertiniaExistingConnectionsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/accounting/certinia/export/CertiniaExportPage.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/workspace/accounting/certinia/import/CertiniaDimensionMappingPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/accounting/ClaimOfferPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/accounting/ClaimOfferPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/accounting/dualentry/DualEntryExistingConnectionsPage.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/pages/workspace/accounting/dualentry/DualEntryExistingConnectionsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/accounting/dualentry/export/DualEntryExportPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/accounting/intacct/advanced/DynamicSageIntacctAccountingMethodPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/accounting/intacct/DynamicSageIntacctPrerequisitesPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/accounting/intacct/EnterSageIntacctCredentialsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/accounting/intacct/ExistingConnectionsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/accounting/intacct/export/DynamicSageIntacctNonReimbursableExpensesDestinationPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/accounting/intacct/export/SageIntacctDatePage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/accounting/intacct/export/SageIntacctNonReimbursableExpensesPage.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/workspace/accounting/intacct/export/SageIntacctReimbursableExpensesDestinationPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/accounting/intacct/import/SageIntacctAddUserDimensionPage.tsx" "no-restricted-syntax" 1 +"../../src/pages/workspace/accounting/intacct/import/SageIntacctAddUserDimensionPage.tsx" "rulesdir/no-default-id-values" 1 +"../../src/pages/workspace/accounting/intacct/import/SageIntacctMappingsTypePage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/accounting/intacct/import/SageIntacctToggleMappingsPage.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/pages/workspace/accounting/netsuite/advanced/DynamicNetSuiteAccountingMethodPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/accounting/netsuite/advanced/DynamicNetSuiteAutoSyncPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/accounting/netsuite/advanced/NetSuiteAdvancedPage.tsx" "react/jsx-key" 1 +"../../src/pages/workspace/accounting/netsuite/advanced/NetSuiteCustomFormIDPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/accounting/netsuite/advanced/NetSuiteExpenseReportApprovalLevelSelectPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/accounting/netsuite/advanced/NetSuiteJournalEntryApprovalLevelSelectPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/accounting/netsuite/advanced/NetSuiteVendorBillApprovalLevelSelectPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/accounting/netsuite/export/DynamicNetSuiteDateSelectPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/accounting/netsuite/export/DynamicNetSuiteExportExpensesDestinationSelectPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/accounting/netsuite/export/DynamicNetSuiteExportExpensesJournalPostingPreferenceSelectPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/accounting/netsuite/export/NetSuiteTravelBillingJournalPostingPreferenceSelectPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomersOrProjectSelectPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomersOrProjectsPage.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomersOrProjectsPage.tsx" "no-restricted-syntax" 2 +"../../src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomersOrProjectsPage.tsx" "rulesdir/no-default-id-values" 1 +"../../src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldEdit.tsx" "@typescript-eslint/no-unsafe-type-assertion" 12 +"../../src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteCustomListSelectorPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteImportAddCustomSegmentContent.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteMenuWithTopDescriptionForm.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/subPages/ChooseSegmentTypeStep.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/subPages/ConfirmCustomSegmentList.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/subPages/CustomListMappingStep.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/subPages/CustomSegmentMappingStep.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldView.tsx" "@typescript-eslint/no-unsafe-type-assertion" 7 +"../../src/pages/workspace/accounting/netsuite/import/NetSuiteImportMappingPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/workspace/accounting/netsuite/import/NetSuiteImportPage.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/subPages/NetSuiteTokenSetupContent.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/workspace/accounting/PolicyAccountingPage.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/pages/workspace/accounting/PolicyAccountingPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/workspace/accounting/PolicyAccountingPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/accounting/qbd/advanced/QuickbooksDesktopAccountingMethodPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/accounting/qbd/export/DynamicQuickbooksDesktopCompanyCardExpenseAccountSelectCardPage.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/workspace/accounting/qbd/export/DynamicQuickbooksDesktopCompanyCardExpenseAccountSelectCardPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/accounting/qbd/export/DynamicQuickbooksDesktopOutOfPocketExpenseEntitySelectPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/accounting/qbd/import/QuickbooksDesktopChartOfAccountsPage.tsx" "no-restricted-syntax" 2 +"../../src/pages/workspace/accounting/qbd/import/QuickbooksDesktopChartOfAccountsPage.tsx" "rulesdir/no-default-id-values" 1 +"../../src/pages/workspace/accounting/qbd/import/QuickbooksDesktopClassesPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/accounting/qbd/import/QuickbooksDesktopClassesPage.tsx" "no-restricted-syntax" 3 +"../../src/pages/workspace/accounting/qbd/import/QuickbooksDesktopClassesPage.tsx" "rulesdir/no-default-id-values" 1 +"../../src/pages/workspace/accounting/qbd/import/QuickbooksDesktopCustomersPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/accounting/qbd/import/QuickbooksDesktopCustomersPage.tsx" "no-restricted-syntax" 3 +"../../src/pages/workspace/accounting/qbd/import/QuickbooksDesktopCustomersPage.tsx" "rulesdir/no-default-id-values" 1 +"../../src/pages/workspace/accounting/qbd/import/QuickbooksDesktopImportPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/accounting/qbd/import/QuickbooksDesktopImportPage.tsx" "no-restricted-syntax" 1 +"../../src/pages/workspace/accounting/qbd/import/QuickbooksDesktopImportPage.tsx" "rulesdir/no-default-id-values" 1 +"../../src/pages/workspace/accounting/qbd/import/QuickbooksDesktopItemsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/accounting/qbd/import/QuickbooksDesktopItemsPage.tsx" "no-restricted-syntax" 3 +"../../src/pages/workspace/accounting/qbd/import/QuickbooksDesktopItemsPage.tsx" "rulesdir/no-default-id-values" 1 +"../../src/pages/workspace/accounting/qbd/QuickbooksDesktopExistingConnectionsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/accounting/qbd/QuickBooksDesktopSetupFlowSyncPage.tsx" "rulesdir/no-default-id-values" 2 +"../../src/pages/workspace/accounting/qbd/QuickBooksDesktopSetupPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/accounting/qbd/QuickBooksDesktopSetupPage.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/pages/workspace/accounting/qbd/RequireQuickBooksDesktopPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/accounting/qbo/advanced/DynamicQuickbooksOnlineAccountingMethodPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/accounting/qbo/advanced/DynamicQuickbooksOnlineAutoSyncPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/accounting/qbo/export/DynamicQuickbooksCompanyCardExpenseAccountPage.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/workspace/accounting/qbo/export/DynamicQuickbooksCompanyCardExpenseAccountSelectCardPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/accounting/qbo/export/DynamicQuickbooksOutOfPocketExpenseEntitySelectPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/accounting/qbo/import/QuickbooksChartOfAccountsPage.tsx" "no-restricted-syntax" 2 +"../../src/pages/workspace/accounting/qbo/import/QuickbooksChartOfAccountsPage.tsx" "rulesdir/no-default-id-values" 1 +"../../src/pages/workspace/accounting/qbo/import/QuickbooksClassesPage.tsx" "rulesdir/no-default-id-values" 1 +"../../src/pages/workspace/accounting/qbo/import/QuickbooksCustomersDisplayedAsPage.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/workspace/accounting/qbo/import/QuickbooksCustomersPage.tsx" "no-restricted-syntax" 2 +"../../src/pages/workspace/accounting/qbo/import/QuickbooksCustomersPage.tsx" "rulesdir/no-default-id-values" 1 +"../../src/pages/workspace/accounting/qbo/import/QuickbooksImportPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/accounting/qbo/import/QuickbooksImportPage.tsx" "no-restricted-syntax" 2 +"../../src/pages/workspace/accounting/qbo/import/QuickbooksImportPage.tsx" "rulesdir/no-default-id-values" 1 +"../../src/pages/workspace/accounting/qbo/import/QuickbooksLocationsDisplayedAsPage.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx" "no-restricted-syntax" 2 +"../../src/pages/workspace/accounting/qbo/import/QuickbooksTaxesPage.tsx" "rulesdir/no-default-id-values" 1 +"../../src/pages/workspace/accounting/reconciliation/CardReconciliationPage.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/workspace/accounting/reconciliation/CardReconciliationPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/accounting/reconciliation/CardReconciliationPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/accounting/rillet/export/RilletExportPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/accounting/rillet/RilletExistingConnectionsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/accounting/utils.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/workspace/accounting/utils.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/accounting/xero/advanced/DynamicXeroAccountingMethodPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/accounting/xero/advanced/DynamicXeroAutoSyncPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/accounting/xero/export/DynamicXeroExportConfigurationPage.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/workspace/accounting/xero/export/DynamicXeroPurchaseBillDateSelectPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/accounting/xero/export/DynamicXeroPurchaseBillStatusSelectorPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/accounting/xero/import/XeroChartOfAccountsPage.tsx" "@typescript-eslint/no-deprecated/fontSize" 1 +"../../src/pages/workspace/accounting/xero/import/XeroChartOfAccountsPage.tsx" "no-restricted-syntax" 2 +"../../src/pages/workspace/accounting/xero/import/XeroChartOfAccountsPage.tsx" "rulesdir/no-default-id-values" 1 +"../../src/pages/workspace/accounting/xero/import/XeroCustomerConfigurationPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/accounting/xero/import/XeroCustomerConfigurationPage.tsx" "no-restricted-syntax" 3 +"../../src/pages/workspace/accounting/xero/import/XeroCustomerConfigurationPage.tsx" "rulesdir/no-default-id-values" 1 +"../../src/pages/workspace/accounting/xero/XeroImportPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/accounting/xero/XeroImportPage.tsx" "no-restricted-syntax" 1 +"../../src/pages/workspace/accounting/xero/XeroImportPage.tsx" "rulesdir/no-default-id-values" 1 +"../../src/pages/workspace/accounting/xero/XeroMapTrackingCategoryConfigurationPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/workspace/accounting/xero/XeroTaxesConfigurationPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/accounting/xero/XeroTaxesConfigurationPage.tsx" "no-restricted-syntax" 4 +"../../src/pages/workspace/accounting/xero/XeroTaxesConfigurationPage.tsx" "rulesdir/no-default-id-values" 1 +"../../src/pages/workspace/accounting/xero/XeroTrackingCategoryConfigurationPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/workspace/categories/CategoryGLCodePage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/categories/CategoryPayrollCodePage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/categories/CategorySettingsPage.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/pages/workspace/categories/CategorySettingsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/categories/DynamicCategoryApproverPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/categories/DynamicCategoryDefaultTaxRatePage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/categories/DynamicCategoryDescriptionHintPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/categories/DynamicCategoryFlagAmountsOverPage.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/workspace/categories/DynamicCategoryFlagAmountsOverPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/categories/DynamicCategoryRequiredFieldsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/categories/DynamicCategoryRequireItemizedReceiptsOverPage.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/workspace/categories/DynamicCategoryRequireItemizedReceiptsOverPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/categories/DynamicCategoryRequireReceiptsOverPage.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/workspace/categories/DynamicCategoryRequireReceiptsOverPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/categories/DynamicCreateCategoryPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/categories/DynamicDefaultCategorySelectorPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/categories/DynamicEditCategoryPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/categories/DynamicExpenseLimitTypeSelectorPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/categories/ImportedCategoriesPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/categories/WorkspaceCategoriesPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/categories/WorkspaceCategoriesPage.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/pages/workspace/companyCards/addNew/AmexCustomFeed.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/companyCards/addNew/CardInstructionsStep.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/companyCards/addNew/CardNameStep.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/companyCards/addNew/CardTypeStep.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/companyCards/addNew/CompanyCardLayoutNamePage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/companyCards/addNew/CompanyCardsImportedPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/companyCards/addNew/DetailsStep.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/companyCards/addNew/ImportFromFileStep.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/companyCards/addNew/PlaidConnectionStep.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/companyCards/addNew/SelectBankStep.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/companyCards/addNew/SelectCountryStep.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/companyCards/addNew/SelectCountryStep.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/companyCards/addNew/SelectFeedType.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/companyCards/assignCard/CardNameStep.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/companyCards/assignCard/TransactionStartDateStep.tsx" "react-hooks/purity" 1 +"../../src/pages/workspace/companyCards/BankConnection/index.native.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/companyCards/BankConnection/index.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/companyCards/BankConnection/index.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/pages/workspace/companyCards/DynamicWorkspaceCompanyCardDetailsPage.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/pages/workspace/companyCards/DynamicWorkspaceCompanyCardDetailsPage.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/workspace/companyCards/DynamicWorkspaceCompanyCardDetailsPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/companyCards/DynamicWorkspaceCompanyCardDetailsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/companyCards/WorkspaceCompanyCardAddWorkEmailPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/workspace/companyCards/WorkspaceCompanyCardAddWorkEmailPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/companyCards/WorkspaceCompanyCardEditCardNamePage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/companyCards/WorkspaceCompanyCardEditTransactionStartDatePage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/companyCards/WorkspaceCompanyCardEditTransactionStartDatePage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/companyCards/WorkspaceCompanyCardFeedSelectorPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/workspace/companyCards/WorkspaceCompanyCardFeedSelectorPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/companyCards/WorkspaceCompanyCardPageEmptyState.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/workspace/companyCards/WorkspaceCompanyCardsErrorConfirmation.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/companyCards/WorkspaceCompanyCardsSettingsFeedNamePage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/companyCards/WorkspaceCompanyCardsSettingsPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/companyCards/WorkspaceCompanyCardsSettingsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/companyCards/WorkspaceCompanyCardStatementCloseDateSelectionList/index.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/workspace/companyCards/WorkspaceCompanyCardStatementCloseDateSelectionList/index.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/companyCards/WorkspaceVerifyWorkAccountPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/workspace/companyCards/WorkspaceVerifyWorkAccountPage.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/pages/workspace/ConnectExistingBusinessBankAccountPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/connections/ConnectionCard.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/pages/workspace/connections/ConnectionsAccountingPage.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/pages/workspace/connections/ConnectionsMergeProviderCard.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/workspace/connections/ConnectionsMergeProviderCard.tsx" "@typescript-eslint/no-deprecated/RowMenuItem" 1 +"../../src/pages/workspace/connections/ConnectionsReceiptPartnersPage.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/pages/workspace/copyPolicySettings/CopyPolicySettingsConfirmPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/copyPolicySettings/CopyPolicySettingsSelectFeaturesPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/copyPolicySettings/CopyPolicySettingsSelectWorkspacesPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/copyPolicySettings/CopyPolicySettingsUpgradePage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/distanceRates/PolicyCommuterExclusionsPage.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/workspace/distanceRates/PolicyCommuterExclusionsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/distanceRates/PolicyDistanceRateDateEditPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/distanceRates/PolicyDistanceRateEditPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/distanceRates/PolicyDistanceRateNameEditPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/distanceRates/PolicyDistanceRatesSettingsPage.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 3 +"../../src/pages/workspace/distanceRates/PolicyDistanceRatesSettingsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/distanceRates/PolicyDistanceRateTaxRateEditPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/distanceRates/PolicyDistanceRateTaxReclaimableEditPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/distanceRates/PolicyDistanceRateUnitPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/downgrade/DynamicPayAndDowngradePage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/downgrade/DynamicWorkspaceDowngradePage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/duplicate/WorkspaceDuplicateForm.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/duplicate/WorkspaceDuplicateSelectFeaturesForm.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/duplicate/WorkspaceDuplicateSelectFeaturesForm.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/pages/workspace/DynamicWorkspaceConfirmationPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/DynamicWorkspaceInvitePage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/DynamicWorkspaceOverviewPlanTypePage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/workspace/DynamicWorkspaceOverviewPlanTypePage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/DynamicWorkspaceOverviewPlanTypePage.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/pages/workspace/DynamicWorkspacePlanTypeSelectionPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/expensifyCard/DynamicExpensifyCardLimitPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/expensifyCard/DynamicExpensifyCardLimitTypePage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/expensifyCard/DynamicExpensifyCardLimitTypePage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/expensifyCard/DynamicExpensifyCardLimitTypePage.tsx" "react-hooks/purity" 2 +"../../src/pages/workspace/expensifyCard/DynamicExpensifyCardNamePage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/expensifyCard/DynamicWorkspaceExpensifyCardDetailsPage.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/pages/workspace/expensifyCard/DynamicWorkspaceExpensifyCardDetailsPage.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/workspace/expensifyCard/DynamicWorkspaceExpensifyCardDetailsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/expensifyCard/DynamicWorkspaceSettlementAccountPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/pages/workspace/expensifyCard/DynamicWorkspaceSettlementAccountPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/expensifyCard/issueNew/CardTypeStep.tsx" "@typescript-eslint/no-deprecated/MenuItem" 2 +"../../src/pages/workspace/expensifyCard/issueNew/LimitTypeStep.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/expensifyCard/issueNew/spendRules/SetSpendRulesStep.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/expensifyCard/issueNew/spendRules/SetSpendRulesStep.tsx" "react-hooks/purity" 2 +"../../src/pages/workspace/expensifyCard/issueNew/spendRules/SpendRuleSelectionPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/expensifyCard/WorkspaceCardSettingsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/expensifyCard/WorkspaceCardsListLabel.tsx" "react-hooks/purity" 1 +"../../src/pages/workspace/expensifyCard/WorkspaceExpensifyCardAddWorkEmailPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/expensifyCard/WorkspaceExpensifyCardBankAccounts.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/pages/workspace/expensifyCard/WorkspaceExpensifyCardBankAccounts.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/expensifyCard/WorkspaceExpensifyCardBankAccounts.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/expensifyCard/WorkspaceExpensifyCardFeedSelectorPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/expensifyCard/WorkspaceExpensifyCardListPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/expensifyCard/WorkspaceSettlementFrequencyPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/fields/CreateFieldsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/fields/FieldsAddListValuePage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/fields/FieldsEditValuePage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/fields/FieldsInitialListValuePage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/fields/FieldsInitialValuePage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/fields/FieldsListValuesPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/fields/FieldsSettingsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/fields/FieldsTypeSelectorPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/fields/FieldsValueSettingsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/fields/WorkspaceFieldsSection.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/pages/workspace/hr/HRApprovalModePageBase.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/hr/merge/MergeHRGroupsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/hr/utils.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/invoices/WorkspaceInvoiceBalanceSection.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/workspace/invoices/WorkspaceInvoiceVBASection.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/workspace/invoices/WorkspaceInvoiceVBASection.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/workspace/invoices/WorkspaceInvoicingDetailsName.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/invoices/WorkspaceInvoicingDetailsSection.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 2 +"../../src/pages/workspace/invoices/WorkspaceInvoicingDetailsWebsite.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/mcp/WorkspaceMCPPage.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/pages/workspace/mcp/WorkspaceMCPPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/members/DynamicWorkspaceOwnerChangeErrorPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/members/DynamicWorkspaceOwnerChangeErrorPage.tsx" "react-hooks/refs" 2 +"../../src/pages/workspace/members/DynamicWorkspaceOwnerChangeSuccessPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/members/DynamicWorkspaceOwnerChangeSuccessPage.tsx" "react-hooks/refs" 2 +"../../src/pages/workspace/members/DynamicWorkspaceOwnerChangeWrapperPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/members/ImportedMembersConfirmationPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/members/ImportedMembersConfirmationPage.tsx" "react-hooks/refs" 7 +"../../src/pages/workspace/members/ImportedMembersPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/members/ImportMembersPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/members/ImportMembersPage.tsx" "no-restricted-syntax" 1 +"../../src/pages/workspace/members/ImportMembersPage.tsx" "rulesdir/no-default-id-values" 1 +"../../src/pages/workspace/members/WorkspaceInviteMessageComponent.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/members/WorkspaceInviteMessageComponent.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/members/WorkspaceInviteMessageComponent.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/pages/workspace/members/WorkspaceMemberCustomFieldPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 2 +"../../src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/members/WorkspaceOwnerChangeCheck.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/pages/workspace/members/WorkspaceOwnerPaymentCardForm.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/workspace/members/WorkspaceOwnerPaymentCardForm.tsx" "react-hooks/set-state-in-effect" 2 +"../../src/pages/workspace/merge/MergeConnectionsPageBase.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/merge/MergeProviderCard.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/pages/workspace/merge/MergeProviderCard.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/workspace/merge/MergeProviderCard.tsx" "@typescript-eslint/no-deprecated/RowMenuItem" 1 +"../../src/pages/workspace/perDiem/EditPerDiemAmountPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/perDiem/EditPerDiemCurrencyPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/perDiem/EditPerDiemDestinationPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/perDiem/EditPerDiemSubratePage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/perDiem/ImportedPerDiemPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/perDiem/WorkspacePerDiemDetailsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/perDiem/WorkspacePerDiemPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/perDiem/WorkspacePerDiemSettingsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/receiptPartners/ChangeReceiptBillingAccountPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/receiptPartners/DynamicEditInviteReceiptPartnerPolicyPage.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/workspace/receiptPartners/DynamicEditInviteReceiptPartnerPolicyPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/workspace/receiptPartners/DynamicEditInviteReceiptPartnerPolicyPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/receiptPartners/DynamicInviteReceiptPartnerPolicyPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/receiptPartners/WorkspaceReceiptPartnersPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/recruiting/approver/MergeATSApprovalModePage.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/workspace/recruiting/merge/filters/MergeATSFiltersPage.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/workspace/reports/InitialListValueSelector/index.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/workspace/reports/ReportsDefaultTitle.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/reports/TypeSelector/index.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/workspace/reports/TypeSelector/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/reports/WorkspaceReportsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/rooms/WorkspaceRoomsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/rules/AgentRules/AddAgentRulePage.tsx" "react/no-unstable-nested-components" 1 +"../../src/pages/workspace/rules/ExpenseReportRulesSection.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 3 +"../../src/pages/workspace/rules/FlagForReviewRules/FlagForReviewRuleAmountPageBase.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/workspace/rules/FlagForReviewRules/FlagForReviewRulePageBase.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/workspace/rules/IndividualExpenseRulesSectionRevamp.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/pages/workspace/rules/MerchantRules/ExpenseDefaultTypePage.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/pages/workspace/rules/MerchantRules/MerchantRulePageBase.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/workspace/rules/MerchantTypeRules/MerchantTypeRulePageBase.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/workspace/rules/RulesNewPage.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/pages/workspace/rules/SpendRules/SpendRulePageBase.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/workspace/tags/DynamicEditTagPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/tags/DynamicTagApproverPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/tags/DynamicTagGLCodePage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/tags/DynamicTagSettingsPage.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/workspace/tags/DynamicTagSettingsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/tags/DynamicWorkspaceEditTagsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/tags/DynamicWorkspaceViewTagsPage.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/workspace/tags/DynamicWorkspaceViewTagsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/tags/ImportedMultiLevelTagsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/tags/ImportedTagsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/tags/ImportMultiLevelTagsSettingsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/tags/ImportTagsOptionsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/tags/WorkspaceCreateTagPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/tags/WorkspaceTagsPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/pages/workspace/tags/WorkspaceTagsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/tags/WorkspaceTagsPage.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/pages/workspace/taxes/NamePage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/taxes/ValuePage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/taxes/WorkspaceCreateTaxPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/taxes/WorkspaceCreateTaxValuePage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/taxes/WorkspaceEditTaxPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/taxes/WorkspaceTaxCodePage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/taxes/WorkspaceTaxesPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/taxes/WorkspaceTaxesPage.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/pages/workspace/taxes/WorkspaceTaxesSettingsCustomTaxName.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/taxes/WorkspaceTaxesSettingsForeignCurrency.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/taxes/WorkspaceTaxesSettingsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/taxes/WorkspaceTaxesSettingsWorkspaceCurrency.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/timeTracking/WorkspaceTimeTrackingDefaultRatePage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/travel/PolicyTravelPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/travel/WorkspaceTravelBillingExportPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/travel/WorkspaceTravelBillingFeedSelectorPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/travel/WorkspaceTravelBillingMonthlyLimitPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/travel/WorkspaceTravelBillingSection.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../src/pages/workspace/travel/WorkspaceTravelBillingSettlementAccountPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/travel/WorkspaceTravelBillingSettlementAccountPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/travel/WorkspaceTravelBillingSettlementFrequencyPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/upgrade/GenericFeaturesView.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/workspace/upgrade/UpgradeIntro.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/workspace/upgrade/WorkspaceUpgradePage.tsx" "no-restricted-imports" 2 +"../../src/pages/workspace/vendors/WorkspaceVendorsPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/withPolicy.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/workspace/withPolicy.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/pages/workspace/withPolicyAndFullscreenLoading.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/withPolicyConnections.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/workflows/approvals/ApprovalWorkflowEditor.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 2 +"../../src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsApprovalLimitPage.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 2 +"../../src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsApprovalLimitPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsCreatePage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/workflows/tabs/WorkflowsPaymentsTab.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/pages/workspace/workflows/tabs/WorkflowsPaymentsTab.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/workspace/workflows/tabs/WorkflowsPaymentsTab.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/workflows/WorkspaceWorkflowsCurrencyConversionFeesPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/workflows/WorkspaceWorkflowsPayerPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/WorkspaceConfirmationOwnerSelectorPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/WorkspaceConfirmationSuccessPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/WorkspaceInitialPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/WorkspaceMembersPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/WorkspaceMoreFeaturesPage/index.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/WorkspaceNamePage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/WorkspaceNewRoomPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/pages/workspace/WorkspaceNewRoomPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/WorkspaceNewRoomPage.tsx" "react-hooks/set-state-in-effect" 3 +"../../src/pages/workspace/WorkspaceOverviewClientIDPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/WorkspaceOverviewCurrencyPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/WorkspaceOverviewDescriptionPage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/WorkspaceOverviewPage.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 3 +"../../src/pages/workspace/WorkspaceOverviewPage.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/workspace/WorkspaceOverviewPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/workspace/WorkspaceOverviewSharePage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/pages/workspace/WorkspaceOverviewSharePage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/WorkspacePageWithSections.tsx" "no-restricted-imports" 2 +"../../src/pages/workspace/WorkspacePageWithSections.tsx" "react-hooks/preserve-manual-memoization" 2 +"../../src/polyfills/requestIdleCallback.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/ROUTES.ts" "@typescript-eslint/no-deprecated/getUrlWithBackToParam" 53 +"../../src/ROUTES.ts" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/selectors/AdvancedSearchFiltersForm.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/selectors/Domain.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/selectors/Policy.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/setup/addUtilsToWindow.ts" "@typescript-eslint/no-unsafe-type-assertion" 20 +"../../src/setup/index.ts" "import/no-named-as-default" 1 +"../../src/setup/moduleInitPolyfill.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/setup/platformSetup/index.native.ts" "no-empty-function" 1 +"../../src/setup/telemetry/index.ts" "no-empty-function" 1 +"../../src/setup/telemetry/index.web.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/stories/AddressSearch.stories.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/stories/ButtonWithDropdownMenu.stories.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/stories/DragAndDrop.stories.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/stories/EReceipt.stories.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/stories/Form.stories.tsx" "@typescript-eslint/no-unsafe-type-assertion" 10 +"../../src/stories/HeaderWithBackButton.stories.tsx" "no-restricted-imports" 2 +"../../src/stories/MenuItem.stories.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/stories/MenuItemComparison.stories.tsx" "@typescript-eslint/no-deprecated/MenuItem" 29 +"../../src/stories/MenuItemComparison.stories.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 22 +"../../src/stories/PopoverMenu.stories.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 +"../../src/stories/ReportActionItemImages.stories.tsx" "no-restricted-syntax" 1 +"../../src/stories/TextInput.stories.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/styles/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/styles/index.ts" "rulesdir/no-raw-typography" 27 +"../../src/styles/theme/utils.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/styles/utils/autoCompleteSuggestion/index.web.ts" "no-restricted-syntax" 1 +"../../src/styles/utils/cursor/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 13 +"../../src/styles/utils/FontUtils/fontFamily/multiFontFamily/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/styles/utils/generators/TooltipStyleUtils/isOverlappingAtTop/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/styles/utils/index.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../src/styles/utils/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/styles/utils/index.ts" "rulesdir/no-raw-typography" 4 +"../../src/TIMEZONES.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/types/expo.d.ts" "no-redeclare" 1 +"../../src/types/form/SpendRuleForm.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/types/onyx/Account.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/AccountData.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/BankAccount.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/CancellationDetails.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/CardFeeds.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/Domain.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/DomainErrors.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/DomainPendingActions.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/DuplicateWorkspace.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/ExpenseRule.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/ExpensifyCardSettings.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/ExportTemplate.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/Fund.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/Login.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/PendingContactAction.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/PersonalBankAccount.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/PersonalCard.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/PersonalDetails.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/PlaidData.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/Policy.ts" "no-restricted-syntax" 2 +"../../src/types/onyx/PolicyCategory.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/PolicyEmployee.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/PolicyJoinMember.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/PolicyTag.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/PrivatePersonalDetails.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/PrivateSubscription.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/RecentWaypoint.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/ReimbursementAccount.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/Report.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/ReportAction.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/ReportActionReactions.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/ReportMetadata.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/ReportNameValuePairs.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/SaveSearch.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/ShareBankAccount.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/Transaction.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/UnshareBankAccount.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/UserWallet.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/VacationDelegate.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/ValidateDomainTwoFactorCode.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/VerifyValidateCodeAction.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/WalletAdditionalDetails.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/WalletOnfido.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/WalletTerms.ts" "no-restricted-syntax" 1 +"../../src/types/onyx/WalletTransfer.ts" "no-restricted-syntax" 1 +"../../src/types/utils/asMutable.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/types/utils/callOrReturn.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/types/utils/CollectionDataSet.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/types/utils/EmptyObject.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/types/utils/getEmptyArray.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/types/utils/htmlDivElementRef.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/types/utils/ObjectUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/types/utils/textRef.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/types/utils/viewRef.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/utils/createDummySharedValue.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/utils/times.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../tests/actions/AppTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/actions/BankAccountsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/actions/EnforceActionExportRestrictions.ts" "no-restricted-imports" 1 +"../../tests/actions/IOU/BuildOnyxDataForMoneyRequestTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 6 +"../../tests/actions/IOU/CreateDraftTransactionTest.ts" "no-restricted-imports" 1 +"../../tests/actions/IOU/MoneyRequestBuilderTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 2 +"../../tests/actions/IOU/MoneyRequestBuilderTest.ts" "no-restricted-imports" 1 +"../../tests/actions/IOU/MoneyRequestSettersTest.ts" "no-restricted-imports" 1 +"../../tests/actions/IOU/MoneyRequestTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/actions/IOU/MoneyRequestTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../tests/actions/IOU/RequestMoneyTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/actions/IOU/RequestMoneyTest.ts" "no-restricted-imports" 1 +"../../tests/actions/IOU/SearchUpdateTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../tests/actions/IOU/SearchUpdateTest.ts" "no-restricted-imports" 1 +"../../tests/actions/IOU/SplitExpenseItemsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 6 +"../../tests/actions/IOU/SplitReportTotalsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 10 +"../../tests/actions/IOU/SplitReportTotalsTest.ts" "no-restricted-imports" 1 +"../../tests/actions/IOUTest/GetUpdateMoneyRequestParamsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 4 +"../../tests/actions/IOUTest/HoldTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/actions/IOUTest/HoldTest.ts" "no-restricted-imports" 1 +"../../tests/actions/IOUTest/PayMoneyRequestTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/actions/IOUTest/PayMoneyRequestTest.ts" "no-restricted-imports" 1 +"../../tests/actions/IOUTest/ReceiptTest.ts" "no-restricted-imports" 1 +"../../tests/actions/IOUTest/SplitSelfDMTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/actions/IOUTest/SplitTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../tests/actions/IOUTest/TrackExpenseTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 4 +"../../tests/actions/IOUTest/TrackExpenseTest.ts" "no-restricted-imports" 1 +"../../tests/actions/IOUTest/UpdateMoneyRequestTest.ts" "no-restricted-imports" 1 +"../../tests/actions/IOUTest/UpdateMoneyRequestVendorTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/actions/MergeTransactionTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 5 +"../../tests/actions/OdometerTransactionUtilsTest.ts" "no-restricted-imports" 1 +"../../tests/actions/OnyxUpdateManagerTest.ts" "no-restricted-imports" 1 +"../../tests/actions/PolicyCategoryTest.ts" "no-restricted-imports" 1 +"../../tests/actions/PolicyProfileTest.ts" "no-restricted-imports" 1 +"../../tests/actions/PolicyTagTest.ts" "no-restricted-imports" 1 +"../../tests/actions/PolicyTaxTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 8 +"../../tests/actions/PolicyTaxTest.ts" "no-unsafe-optional-chaining" 1 +"../../tests/actions/ReportFieldTest.ts" "no-restricted-imports" 1 +"../../tests/actions/ReportPreviewActionUtilsTest.ts" "no-restricted-imports" 2 +"../../tests/actions/ReportTest.ts" "no-restricted-imports" 2 +"../../tests/actions/TaskTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/actions/TaskTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/actions/TaskTest.ts" "no-restricted-imports" 1 +"../../tests/actions/TransactionTest.ts" "no-restricted-imports" 1 +"../../tests/navigation/addCardToDigitalWalletUrlTests.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/navigation/cleanStaleReportActionBackToParamTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/navigation/ensureTabNavigatorRoutesTests.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/navigation/findFocusedRouteWithOnyxTabGuardTests.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/navigation/getPathFromStateTests.ts" "@typescript-eslint/no-unnecessary-type-assertion" 3 +"../../tests/navigation/LinkedActionNotFoundGuardTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/navigation/OneTransactionThreadRedirectHandlerOnyxTest.tsx" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/navigation/splitExpenseDynamicRouteTests.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/perf-local/OptionsListChatPreview.perf-local.ts" "@typescript-eslint/no-unnecessary-type-assertion" 5 +"../../tests/perf-local/OptionsListChatPreview.perf-local.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../tests/perf-test/DynamicReportDetailsPage.perf-test.tsx" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/perf-test/DynamicReportDetailsPage.perf-test.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../tests/perf-test/MoneyRequestReportActionsList.perf-test.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/perf-test/ReportActionCompose.perf-test.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/ui/AddAgentRulePageTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../tests/ui/AgentRulesSectionTest.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../tests/ui/AgentRulesSectionTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../tests/ui/AgentsPromoBannersTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/ui/AncestorReportActionItemTest.tsx" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/ui/ApproverSelectionListTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 3 +"../../tests/ui/BaseModalTest.tsx" "react-hooks/globals" 1 +"../../tests/ui/CategorySelectorTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 8 +"../../tests/ui/ChangeReceiptBillingAccountPageTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../tests/ui/ChatActionableButtonsTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../tests/ui/ChronosTimerHeaderButton.test.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/ui/ClearReportActionErrorsUITest.tsx" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/ui/components/CompactMenuContextTest.tsx" "@typescript-eslint/no-deprecated/MenuItem" 6 +"../../tests/ui/components/HeaderViewTest.tsx" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/ui/components/IOURequestStepConfirmationPageTest.tsx" "no-restricted-imports" 1 +"../../tests/ui/components/MenuItemTest.tsx" "@typescript-eslint/no-deprecated/MenuItem" 7 +"../../tests/ui/components/MoneyReportHeaderActionsPlacementTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../tests/ui/components/MoneyReportHeaderFocusSelectionModeTest.tsx" "no-restricted-imports" 1 +"../../tests/ui/components/MoneyReportHeaderSelectionModeTest.tsx" "no-restricted-imports" 1 +"../../tests/ui/components/MoneyRequestHeaderSecondaryActionsTest.tsx" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/ui/components/SettlementButtonTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 7 +"../../tests/ui/ComposerImperativeRefTest.tsx" "@typescript-eslint/no-deprecated/keyCode" 1 +"../../tests/ui/ComposerTest.tsx" "rulesdir/no-raw-typography" 1 +"../../tests/ui/DomainNameOrNotFoundWrapperTest.tsx" "@typescript-eslint/no-unsafe-member-access" 1 +"../../tests/ui/DynamicCategoryDefaultTaxRatePageTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/ui/DynamicReportDetailsPageTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../tests/ui/DynamicWorkspaceWorkflowsApprovalsExpensesFromPageFastEditSaveTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/ui/DynamicWorkspaceWorkflowsApprovalsExpensesFromPageTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/ui/ExpensifyCodePageTest.tsx" "@typescript-eslint/no-unsafe-return" 1 +"../../tests/ui/ImportedMembersPageTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../tests/ui/InitialSettingsPageTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/ui/IOURequestStepDistanceManualTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../tests/ui/IOURequestStepDistanceOdometerBackupTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../tests/ui/IOURequestStepDistanceOdometerDiscardGuardTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../tests/ui/IOURequestStepDistanceOdometerNextSyncTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../tests/ui/IOURequestStepDistanceOdometerSflResumeTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../tests/ui/IOURequestStepDistanceRateTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 4 +"../../tests/ui/IOURequestStepDistanceTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/ui/IssueNewCardAssigneeStepTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../tests/ui/IssueNewCardConfirmValidateCodePageTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../tests/ui/IssueNewCardConfirmValidateCodePageTest.tsx" "no-unsafe-optional-chaining" 1 +"../../tests/ui/MoneyReportViewTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../tests/ui/MoneyRequestReportActionsListRejectModalTest.tsx" "@typescript-eslint/no-deprecated/originalMessage" 2 +"../../tests/ui/MoneyRequestReportActionsListRejectModalTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 10 +"../../tests/ui/MoneyRequestReportPreview.test.tsx" "@typescript-eslint/no-deprecated/originalMessage" 5 +"../../tests/ui/MoneyRequestReportPreview.test.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 4 +"../../tests/ui/MoneyRequestReportPreview.test.tsx" "no-restricted-imports" 1 +"../../tests/ui/MoneyRequestViewReceiptTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../tests/ui/MoneyRequestViewTest.tsx" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/ui/MyTripsPageTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../tests/ui/NumericBigNumberPadTest.tsx" "no-inner-declarations" 1 +"../../tests/ui/ProfilePageTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 5 +"../../tests/ui/PublicProfilePageTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 4 +"../../tests/ui/PushRowWithModalTest.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 +"../../tests/ui/RecentlyAddedSectionTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/ui/ReportActionItemTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../tests/ui/ReportActionItemTest.tsx" "react-hooks/globals" 1 +"../../tests/ui/ReportActionsListTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/ui/ReportActionsListTest.tsx" "no-unsafe-optional-chaining" 2 +"../../tests/ui/RulesNewPageDescribeRuleTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../tests/ui/SearchEditMultiplePageTest.tsx" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/ui/SearchMoneyRequestReportPageTest.tsx" "@typescript-eslint/no-deprecated/originalMessage" 2 +"../../tests/ui/SearchMoneyRequestReportPageTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 3 +"../../tests/ui/SearchPageTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 9 +"../../tests/ui/SearchRouterContextualSuggestionTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/ui/ServerSelectionTest.tsx" "no-restricted-imports" 1 +"../../tests/ui/ShareSubmitScanFieldsTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/ui/SubmitDetailsPageTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/ui/TableTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 3 +"../../tests/ui/TagSelectorTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 5 +"../../tests/ui/TransactionPreviewContentTest.tsx" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/ui/TransactionReceiptOdometerReplaceTest.tsx" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/ui/UnreadIndicatorsTest.tsx" "no-restricted-imports" 1 +"../../tests/ui/WorkArrangementPageTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/ui/WorkspaceInviteMessageDEWApproverRowTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/ui/WorkspaceInviteMessageFastEditSaveTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/ui/WorkspaceInvoiceVBASectionTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/ui/WorkspaceMoreFeaturesPageTest.tsx" "no-restricted-imports" 1 +"../../tests/ui/WorkspacePageWithSectionsTest.tsx" "@typescript-eslint/no-unsafe-member-access" 1 +"../../tests/ui/WorkspaceWorkflowsApprovalsCreatePageTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/ui/WorkspaceWorkflowsApprovalsEditPageTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/ui/WorkspaceWorkflowsDEWHidePeopleTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/ui/WorkspaceWorkflowsLoadMoreTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/ui/WorkspaceWorkflowsLockedBankAccountTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/ui/WorkspaceWorkflowsPayerPageTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../tests/ui/WorkspaceWorkflowsPayerRowTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/ui/WorkspaceWorkflowsSmartLimitApprovalsTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/AccountSwitcherSkeletonViewTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/activeSpansSweepOrderTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/AddExistingExpenseTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/ApiUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 4 +"../../tests/unit/APIWriteWhenReadyTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 3 +"../../tests/unit/backgroundLocationTrackingTaskTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/BankAccountUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 13 +"../../tests/unit/calculateSuperWideRHPWidthTest.ts" "@typescript-eslint/no-unsafe-return" 3 +"../../tests/unit/canEditFieldOfMoneyRequestTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/unit/canEditFieldOfMoneyRequestTest.ts" "@typescript-eslint/no-deprecated/randomReportAction.originalMessage" 3 +"../../tests/unit/canEditFieldOfMoneyRequestTest.ts" "no-restricted-imports" 1 +"../../tests/unit/CategoryUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 5 +"../../tests/unit/ChronosTimerActionTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 2 +"../../tests/unit/ChronosTimerActionTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/cleanupPreMountedDraftReportsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/CollectDepositAccountUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/CompanyCardsRBRTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/components/Avatar/connected/ChatThreadAvatarTest.tsx" "@typescript-eslint/no-deprecated/originalMessage" 6 +"../../tests/unit/components/Form/FormProvider.test.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/components/Form/FormProvider.test.tsx" "import/no-duplicates" 1 +"../../tests/unit/components/HTMLEngineProvider/VictoryChartAttributesParsers.test.ts" "rulesdir/no-raw-typography" 1 +"../../tests/unit/components/MoneyReportTransactionThreadContext.test.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../tests/unit/components/reportDetails/ReportDetailsMenuItemsTest.tsx" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/unit/ComposerLocalTimeTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/ContextMenuActionsCopyMessageTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 6 +"../../tests/unit/ContextMenuActionsDeleteTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/unit/CopyPolicySettingsConfirmTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/CopyPolicySettingsConfirmTest.tsx" "react-hooks/immutability" 1 +"../../tests/unit/CopyPolicySettingsNavigationTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../tests/unit/CopyPolicySettingsUpgradeTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/CopyPolicySettingsUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/CreateReportMenuItemTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/DateUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/DateUtilsTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../tests/unit/DistanceRateTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/DistanceRequestUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/ExpenseDefaultRuleUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/FlaggedExpensesTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 4 +"../../tests/unit/FormulaTest.ts" "no-restricted-imports" 1 +"../../tests/unit/getCentralPaneReportIDTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 3 +"../../tests/unit/getDefaultCardFeedTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 4 +"../../tests/unit/getReportSubmitViolationSummaryTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../tests/unit/getSendMessageListWeightTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../tests/unit/GlobalReimbursementPayErrorTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 3 +"../../tests/unit/goBackFromWorkspaceSettingPagesTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 5 +"../../tests/unit/GPSDraftDetailsActionsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/GPSDraftDetailsUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/HandleMovedScanFailedExpensesTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/unit/HandleMovedScanFailedExpensesTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 3 +"../../tests/unit/HomePage/RecentlyAddedSection/useRecentlyAddedDataTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 4 +"../../tests/unit/HomePage/YourSpendSection/useYourSpendDataTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 6 +"../../tests/unit/HomePage/YourSpendSection/useYourSpendDataTest.ts" "no-restricted-imports" 1 +"../../tests/unit/HomePage/YourSpendSection/YourSpendSectionTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/hooks/useAppReturnCount.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/hooks/useAutocompleteSuggestions.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../tests/unit/hooks/useBlurOnKeyboardHide.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/hooks/useBrokenDirectCompanyCardFeedsForAdmin.test.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../tests/unit/hooks/useChangeTransactionsReportReports.test.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/unit/hooks/useConfirmationSections.test.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/hooks/useConfirmationValidation.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/hooks/useDeleteTransactionsInvoiceOfflineTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 4 +"../../tests/unit/hooks/useDismissOnMoneyRequestReportRemoval.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../tests/unit/hooks/useExpenseActionsInvoiceDeleteTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../tests/unit/hooks/useExpenseSubmission.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 6 +"../../tests/unit/hooks/useFeedKeysWithAssignedCards.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 5 +"../../tests/unit/hooks/useFilesValidation.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 4 +"../../tests/unit/hooks/useFilterPendingDeleteReports.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 9 +"../../tests/unit/hooks/useFormErrorManagement.test.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/hooks/useHoldRejectActionsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/hooks/useHydrateReportsFromSnapshot.test.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 6 +"../../tests/unit/hooks/useInsightsFilters.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/hooks/useNonPersonalCardList.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 3 +"../../tests/unit/hooks/useOdometerReceiptStitcherTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/hooks/usePayChatReportActions.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/hooks/useReportWasDeleted.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 14 +"../../tests/unit/hooks/useSearchBulkActionsDeleteTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/unit/hooks/useSearchBulkActionsDownloadReceiptsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/hooks/useSearchBulkActionsDuplicateTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/hooks/useSearchBulkActionsExportTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/hooks/useSeedMyExpensesSearch.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/hooks/useSelectedTransactionsActions.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 4 +"../../tests/unit/hooks/useSelectionModeReportActions.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../tests/unit/hooks/useSplitEffectivePolicy.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/hooks/useSplitParticipants.test.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/hooks/useTimeSensitiveAddBankAccount.test.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/unit/hooks/useTimeSensitiveAddBankAccount.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/hooks/useTimeSensitiveLockedBankAccount.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/hooks/useTransactionThreadReportID.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 3 +"../../tests/unit/hooks/useTransactionThreadReportIDs.test.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/unit/hooks/useUpcomingTravelReservations.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/ImportedMerchantRulesPageTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/IOUAmountSubmissionTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/IOUUtilsTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/unit/IOUUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 5 +"../../tests/unit/IOUUtilsTest.ts" "no-restricted-imports" 1 +"../../tests/unit/LastVisitedPathOnBackgroundTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../tests/unit/LHNAvatarPipelineTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 7 +"../../tests/unit/LhnSearchPreviewParityTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 25 +"../../tests/unit/LhnSearchPreviewParityTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../tests/unit/libs/receiptStorageTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/libs/TransactionThreadNavigationUtilsTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/unit/libs/TransactionThreadNavigationUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/logSubmittedReceiptMilestoneTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/unit/MerchantRuleSuggestionUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/MergeTransactionUtilsTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/unit/ModifiedExpenseMessageTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 2 +"../../tests/unit/ModifiedExpenseMessageTest.ts" "no-restricted-imports" 1 +"../../tests/unit/navigateAfterOnboardingTest.ts" "no-restricted-imports" 1 +"../../tests/unit/navigateToWorkspacesPageTest.ts" "no-restricted-imports" 1 +"../../tests/unit/Navigation/createRootStackNavigator/handleToggleModalWithHistoryAction.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 3 +"../../tests/unit/Navigation/getIsScreenBlurredWebTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/Navigation/getIsScreenBlurredWebTest.ts" "@typescript-eslint/no-unsafe-return" 1 +"../../tests/unit/Navigation/getLastRouteTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/Navigation/getTabStateWithFocusedTargetTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../tests/unit/Navigation/hasDifferentSplitScope.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 3 +"../../tests/unit/Navigation/preMountBufferTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 8 +"../../tests/unit/NextStepUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 6 +"../../tests/unit/NumberFormatUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../tests/unit/OnboardingFlowTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/OnyxDerived/computeForReportTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 4 +"../../tests/unit/OnyxDerived/visibleReportActionsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../tests/unit/onyxDerivedComputeSpanTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/OnyxDerivedSelfHealTest.ts" "no-restricted-imports" 1 +"../../tests/unit/OnyxDerivedTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 3 +"../../tests/unit/OnyxDerivedTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../tests/unit/OnyxDerivedTest.tsx" "no-restricted-imports" 1 +"../../tests/unit/OptionsListUtilsTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 14 +"../../tests/unit/pages/DynamicReportDetailsPage/useReportDetailsDeleteModalTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/unit/pages/DynamicReportDetailsPage/useReportDetailsReportNameTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/pages/DynamicReportDetailsPage/useReportDetailsRequestDataTest.tsx" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/unit/pages/DynamicReportDetailsPage/useReportDetailsRequestDataTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/pages/inbox/ConciergeDraftContext.test.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/pages/inbox/report/HomeAddressRequiredContent.test.tsx" "@typescript-eslint/no-deprecated/originalMessage" 2 +"../../tests/unit/pages/inbox/report/ReportActionEditMessageContext.test.tsx" "no-restricted-imports" 1 +"../../tests/unit/pages/Search/SearchChangeApproverPageTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/pages/settings/AddAgentPageTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/PaymentUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/performPostBatchCleanupTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/unit/PersistedRequests.ts" "no-restricted-imports" 1 +"../../tests/unit/PolicyUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 3 +"../../tests/unit/preMountDraftReportTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../tests/unit/ProductMarketingWindowTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/QuickActionUtilsTest.ts" "no-restricted-imports" 2 +"../../tests/unit/ReceiptUploadRetryHandlerTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/unit/ReceiptUploadRetryHandlerTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 4 +"../../tests/unit/RenameLegacySageIntacctExportDateTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../tests/unit/ReportActionItemSingleTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/ReportActionsUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 38 +"../../tests/unit/ReportAlternateTextParityTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 15 +"../../tests/unit/ReportAlternateTextParityTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/ReportAlternateTextUtilsTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 67 +"../../tests/unit/ReportAlternateTextUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../tests/unit/reportAttributesTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 2 +"../../tests/unit/ReportNameUtilsTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 33 +"../../tests/unit/ReportPrimaryActionUtilsTest.ts" "no-restricted-imports" 1 +"../../tests/unit/ReportSecondaryActionUtilsTest.ts" "no-restricted-imports" 2 +"../../tests/unit/ReportSelectorTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 6 +"../../tests/unit/ReportTitleUtilsTest.ts" "no-restricted-imports" 1 +"../../tests/unit/ReportUtilsCanShowReportRecipientLocalTimeTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/ReportUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 7 +"../../tests/unit/ReportUtilsTest.ts" "no-restricted-imports" 1 +"../../tests/unit/ReportUtilsThreadSummaryTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/unit/resolveTransactionThreadReportTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/unit/Search/applyShiftRangeBatchToSelectionTest.ts" "@typescript-eslint/consistent-generic-constructors" 2 +"../../tests/unit/Search/ChatSearchViewTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../tests/unit/Search/ExpenseFlatSearchViewTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../tests/unit/Search/ExpenseGroupedSearchViewSplitTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../tests/unit/Search/ExpenseGroupedSearchViewTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../tests/unit/Search/ExpenseReportSearchViewTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../tests/unit/Search/FooterTotalSelectionRetentionTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../tests/unit/Search/LazyGroupSelectionTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/Search/LazyGroupSelectionTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 5 +"../../tests/unit/Search/SearchColumnsPageTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../tests/unit/Search/SearchQueryUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 3 +"../../tests/unit/Search/SearchUIUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 4 +"../../tests/unit/Search/seedMyExpensesSearchTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../tests/unit/Search/TaskSearchViewTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../tests/unit/Search/useOptimisticSearchTrackingTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../tests/unit/Search/useSearchLoadingStateTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../tests/unit/Search/useSearchSnapshotTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../tests/unit/Search/yourSpendApplicabilityTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../tests/unit/SearchActionsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 5 +"../../tests/unit/SearchAutocompleteListTest.tsx" "@typescript-eslint/no-deprecated/originalMessage" 3 +"../../tests/unit/SearchAutocompleteListTest.tsx" "import/no-duplicates" 1 +"../../tests/unit/SearchAutocompleteListTest.tsx" "no-restricted-imports" 1 +"../../tests/unit/SearchEditMultipleUtilsTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/unit/SequentialQueueTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../tests/unit/showReportActionNotificationTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 3 +"../../tests/unit/SidebarLinksTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/SidebarLinksTest.tsx" "react/button-has-type" 1 +"../../tests/unit/SidebarTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 2 +"../../tests/unit/SidebarUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../tests/unit/SidebarUtilsTest.ts" "no-restricted-imports" 1 +"../../tests/unit/StartupDataSpansTest.ts" "@typescript-eslint/no-misused-promises" 1 +"../../tests/unit/submitDismissStrategiesTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/submitWithDismissFirstTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/TodosUtilsTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/unit/TodosUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/TransactionNavigationUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/TransactionPreviewUtils.test.ts" "@typescript-eslint/no-deprecated/originalMessage" 2 +"../../tests/unit/TransactionTest.ts" "no-restricted-imports" 1 +"../../tests/unit/TransactionUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/useAccountIDToNameMapTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/useGPSWaypointMarkersTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/useIsDomainUsingCardTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/useMarkAsReadTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/useMoneyRequestReportDataTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/unit/useMoneyRequestReportPaginationTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/useMoneyRequestReportScrollTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/useMoneyRequestReportSortedTransactionsTest.tsx" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/unit/useMoneyRequestReportVisibleActionsTest.tsx" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/unit/usePersonalDetailSearchSelectorTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../tests/unit/usePreMountDestinationTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/useReceiptHoverZoomTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/useReportIDToNameMapTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/useScreenInitialFocusTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../tests/unit/useSearchPageSetupTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../tests/unit/useSelectionListShortcutsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/useSendMessageSpanMarksTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/useSidebarOrderedReportsTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../tests/unit/useSubPageTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 19 +"../../tests/unit/useTodoCountsTest.tsx" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/unit/useTodoCountsTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/useTransactionViolationOfWorkspaceTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/useTwoFactorAuthRouteTests.ts" "@typescript-eslint/no-unnecessary-type-assertion" 3 +"../../tests/unit/ViolationFixParityTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/WhisperContentMentionContextTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../tests/unit/WorkspacesSettingsUtilsTest.ts" "no-restricted-imports" 1 +"../../tests/utils/collections/card.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/utils/createMock.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../tests/utils/createMockScreenNavigation.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../tests/utils/LHNTestUtils.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/utils/ReportTestUtils.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../wdyr.ts" "import/no-named-as-default" 1 +"../rsbuild/CustomVersionFilePlugin.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../rsbuild/ModuleInitTimingPlugin.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../rsbuild/rsbuild.common.ts" "@typescript-eslint/no-unsafe-argument" 1 +"../rsbuild/rsbuild.common.ts" "@typescript-eslint/no-unsafe-assignment" 1 +"../rsbuild/rsbuild.common.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 diff --git a/config/oxlint/plugins/core-rules.mjs b/config/oxlint/plugins/core-rules.mjs new file mode 100644 index 000000000000..8684a2253672 --- /dev/null +++ b/config/oxlint/plugins/core-rules.mjs @@ -0,0 +1,29 @@ +// no-invalid-this is deliberately absent, even though ESLint enables it: it reaches +// sourceCode.getJSDocComment, which oxlint's bridge answers with `throw Error("...not supported at +// present (and deprecated)")` at node_modules/oxlint/dist/lint.js:5765, erroring out 36 files. +import {builtinRules} from 'eslint/use-at-your-own-risk'; + +import {withEslintDirectiveIdsFor} from '../eslintDirectives.mjs'; + +const NAMES = [ + 'no-restricted-syntax', + 'no-unreachable-loop', + 'lines-between-class-members', + 'no-octal', + 'no-octal-escape', + 'no-undef-init', + 'strict', + 'dot-notation', + 'no-dupe-args', + 'no-return-await', +]; + +const plugin = { + meta: { + name: 'core', + version: '0.0.1', + }, + rules: withEslintDirectiveIdsFor(Object.fromEntries(NAMES.map((name) => [name, builtinRules.get(name)])), (name) => name), +}; + +export default plugin; diff --git a/config/oxlint/plugins/expensify-rules.mjs b/config/oxlint/plugins/expensify-rules.mjs new file mode 100644 index 000000000000..44972fde89f2 --- /dev/null +++ b/config/oxlint/plugins/expensify-rules.mjs @@ -0,0 +1,40 @@ +import fs from 'node:fs'; +import {createRequire} from 'node:module'; +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; + +import {create as preferLocaleCompareCreate, meta as preferLocaleCompareMeta} from '../preferLocaleCompareFromContext.mjs'; +import {withFullGating} from '../reactCompilerGate.mjs'; + +const require = createRequire(import.meta.url); +const pluginDir = path.dirname(fileURLToPath(import.meta.url)); + +const RULE_DIRS = [path.resolve(pluginDir, '../../../node_modules/eslint-config-expensify/eslint-plugin-expensify'), path.resolve(pluginDir, '../../../eslint-plugin-local-rules')]; + +const HELPER_FILES = new Set(['CONST.js']); + +const rules = {}; +for (const dir of RULE_DIRS) { + for (const file of fs.readdirSync(dir)) { + if (!file.endsWith('.js') || HELPER_FILES.has(file)) { + continue; + } + rules[file.replace(/\.js$/, '')] = require(path.join(dir, file)); + } +} + +const plugin = { + meta: { + name: 'rulesdir', + version: '0.0.1', + }, + rules: { + ...rules, + 'no-inline-useOnyx-selector': withFullGating(rules['no-inline-useOnyx-selector']), + // ESLint's copy calls getParserServices at create() time, which throws in a jsPlugin, so a + // type-free rewrite stands in for it. + 'prefer-locale-compare-from-context': {create: preferLocaleCompareCreate, meta: preferLocaleCompareMeta}, + }, +}; + +export default plugin; diff --git a/config/oxlint/plugins/hosted-rules.mjs b/config/oxlint/plugins/hosted-rules.mjs new file mode 100644 index 000000000000..61d48962187f --- /dev/null +++ b/config/oxlint/plugins/hosted-rules.mjs @@ -0,0 +1,97 @@ +import {createRequire} from 'node:module'; +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; + +import {EXHAUSTIVE_DEPS_USECALLBACK_USEMEMO_PATTERN} from '../../reactCompiler/suppressedRules.mjs'; +import {withEslintDirectiveIds, withEslintDirectiveIdsFor} from '../eslintDirectives.mjs'; +import {create as noDeprecatedSelfClosingJsxCreate, meta as noDeprecatedSelfClosingJsxMeta} from '../noDeprecatedSelfClosingJsx.mjs'; +import {withFullGating, withMessageGating} from '../reactCompilerGate.mjs'; +import {hostedRuleNames} from '../ruleNames.mjs'; + +const require = createRequire(import.meta.url); +const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../..'); + +function rulesOf(packageName) { + let mod; + try { + mod = require(path.resolve(repoRoot, 'node_modules', packageName)); + } catch { + // A directory require ignores the package's `exports` map, so a package that ships no `main` + // (typescript-eslint) resolves only by bare specifier. + mod = require(packageName); + } + return mod?.rules ?? mod?.default?.rules ?? {}; +} + +const react = rulesOf('eslint-plugin-react'); +const importPlugin = rulesOf('eslint-plugin-import'); +const jsdoc = rulesOf('eslint-plugin-jsdoc'); +const storybook = rulesOf('eslint-plugin-storybook'); +const typescriptEslint = rulesOf('@typescript-eslint/eslint-plugin'); + +// eslint-plugin-react-hooks is not a root dependency: it arrives nested under eslint-config-expensify, +// and that nested copy is the one ESLint loads. Resolving by bare specifier would run a second install. +const reactHooks = (() => { + const configEntry = require.resolve('eslint-config-expensify', {paths: [repoRoot]}); + return rulesOf(require.resolve('eslint-plugin-react-hooks', {paths: [path.dirname(configEntry)]})); +})(); + +// naming-convention demands parser services at startup, which oxlint's JS plugins cannot provide. +// getParserServices(context, true) tolerates a missing `program` but hard-fails on missing node maps +// and reads context.languageOptions before any check, so both are stubbed. With this repo's selector +// groups the rule never reaches the type checker anyway (no `types` selector). +const STUB_PARSER_SERVICES = { + esTreeNodeToTSNodeMap: new Map(), + tsNodeToESTreeNodeMap: new Map(), +}; + +function withStubbedParserServices(rule) { + return { + ...rule, + create(context) { + const sourceCode = context.sourceCode ?? context.getSourceCode(); + // Prototype shadowing, not a Proxy: oxlint defines `parserServices` as a read-only, + // non-configurable data property, which a Proxy get trap may not report a different value for. + const stubbedSourceCode = Object.create(sourceCode, { + parserServices: {value: STUB_PARSER_SERVICES}, + }); + const stubbedContext = Object.create(context, { + sourceCode: {value: stubbedSourceCode}, + getSourceCode: {value: () => stubbedSourceCode}, + languageOptions: { + value: context.languageOptions ?? {parser: {meta: {name: 'oxlint-js-plugin'}}}, + }, + }); + return rule.create(stubbedContext); + }, + }; +} + +function hostRules(rules, eslintPrefix, names) { + return withEslintDirectiveIdsFor(Object.fromEntries(names.map((name) => [name, rules[name]])), (name) => `${eslintPrefix}/${name}`); +} + +const SEPARATELY_GATED = new Set(['jsx-no-constructed-context-values', 'exhaustive-deps']); + +function plainlyHosted(eslintPrefix) { + return hostedRuleNames(eslintPrefix).filter((name) => !SEPARATELY_GATED.has(name)); +} + +const plugin = { + meta: { + name: 'hosted', + version: '0.0.1', + }, + rules: { + ...hostRules(react, 'react', plainlyHosted('react')), + ...hostRules(importPlugin, 'import', plainlyHosted('import')), + ...hostRules(jsdoc, 'jsdoc', plainlyHosted('jsdoc')), + ...hostRules(storybook, 'storybook', plainlyHosted('storybook')), + ...hostRules({'naming-convention': withStubbedParserServices(typescriptEslint['naming-convention'])}, '@typescript-eslint', plainlyHosted('@typescript-eslint')), + 'jsx-no-constructed-context-values': withFullGating(withEslintDirectiveIds(react['jsx-no-constructed-context-values'], 'react/jsx-no-constructed-context-values')), + 'no-deprecated-self-closing-jsx': withEslintDirectiveIds({create: noDeprecatedSelfClosingJsxCreate, meta: noDeprecatedSelfClosingJsxMeta}, '@typescript-eslint/no-deprecated'), + 'exhaustive-deps': withMessageGating(withEslintDirectiveIds(reactHooks['exhaustive-deps'], 'react-hooks/exhaustive-deps'), EXHAUSTIVE_DEPS_USECALLBACK_USEMEMO_PATTERN), + }, +}; + +export default plugin; diff --git a/config/oxlint/plugins/rc-rules.mjs b/config/oxlint/plugins/rc-rules.mjs new file mode 100644 index 000000000000..48a8801f3bb0 --- /dev/null +++ b/config/oxlint/plugins/rc-rules.mjs @@ -0,0 +1,41 @@ +import {withEslintDirectiveIds} from '../eslintDirectives.mjs'; +import {RULE_BY_CATEGORY, reactCompilerDiagnostics} from '../reactCompilerRust.mjs'; + +function buildRule(ruleName) { + return { + meta: { + type: 'problem', + docs: { + description: `React Compiler: ${ruleName}`, + url: `https://react.dev/reference/eslint-plugin-react-hooks/lints/${ruleName}`, + }, + schema: [], + }, + create(context) { + return { + Program() { + const filename = context.filename ?? context.getFilename(); + const sourceCode = context.sourceCode ?? context.getSourceCode(); + for (const diagnostic of reactCompilerDiagnostics(filename, sourceCode.text)) { + if (diagnostic.ruleName !== ruleName) { + continue; + } + context.report({loc: diagnostic.loc, message: diagnostic.message}); + } + }, + }; + }, + }; +} + +const rules = Object.fromEntries(Object.values(RULE_BY_CATEGORY).map((ruleName) => [ruleName, withEslintDirectiveIds(buildRule(ruleName), `react-hooks/${ruleName}`)])); + +const plugin = { + meta: { + name: 'rc', + version: '0.0.1', + }, + rules, +}; + +export default plugin; diff --git a/config/oxlint/preferLocaleCompareFromContext.mjs b/config/oxlint/preferLocaleCompareFromContext.mjs new file mode 100644 index 000000000000..c870968d854d --- /dev/null +++ b/config/oxlint/preferLocaleCompareFromContext.mjs @@ -0,0 +1,53 @@ +// oxlint's JS plugins get no type information, so the type query ESLint's +// rulesdir/prefer-locale-compare-from-context makes (`isString(getTypeAtLocation(...))`) cannot be made +// here. `localeCompare` exists on exactly one built-in prototype, so the receiver check is dropped +// rather than approximated. The resulting divergence is an object with its own `localeCompare`. +import {createRequire} from 'node:module'; +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; + +const require = createRequire(import.meta.url); +const pluginDir = path.dirname(fileURLToPath(import.meta.url)); +const expensifyPlugin = path.resolve(pluginDir, '../../node_modules/eslint-config-expensify/eslint-plugin-expensify'); + +/** These modules are ESM; `require` hands back the namespace, so unwrap a default if there is one. */ +function load(relative) { + const module = require(path.join(expensifyPlugin, relative)); + return module?.default ?? module; +} + +const CONST = load('CONST.js'); +const {isInTestFile} = require(path.join(expensifyPlugin, 'utils/index.js')); + +const message = CONST.MESSAGE.PREFER_LOCALE_COMPARE_FROM_CONTEXT; + +const meta = { + type: 'problem', + docs: { + description: message, + }, + schema: [], +}; + +function create(context) { + return { + CallExpression(node) { + const filename = context.filename ?? context.getFilename(); + if (isInTestFile(filename)) { + return; + } + + if (node.callee?.type !== 'MemberExpression' || node.callee.property?.name !== 'localeCompare') { + return; + } + + if (!node.callee.object) { + return; + } + + context.report({node, message}); + }, + }; +} + +export {meta, create}; diff --git a/config/oxlint/reactCompilerGate.mjs b/config/oxlint/reactCompilerGate.mjs new file mode 100644 index 000000000000..38311b1b96ba --- /dev/null +++ b/config/oxlint/reactCompilerGate.mjs @@ -0,0 +1,62 @@ +// oxlint has no processor concept and no way to filter a native Rust rule's diagnostics, so the +// ESLint react-compiler-compat processor is replicated here by wrapping `context.report` in a JS plugin. +import path from 'node:path'; + +import {didBothCompilersMemoizeFile} from '../reactCompiler/checkBoth.mjs'; + +const memoizationCache = new Map(); + +function isFileMemoizedByBothCompilers(filename, sourceText) { + if (!memoizationCache.has(filename)) { + const normalized = filename.split(path.sep).join('/'); + const skipped = normalized.includes('/tests/') || normalized.startsWith('tests/') || normalized.includes('node_modules/'); + memoizationCache.set(filename, skipped ? false : didBothCompilersMemoizeFile(sourceText, filename)); + } + return memoizationCache.get(filename); +} + +function resolveMessage(rule, descriptor) { + if (typeof descriptor?.message === 'string') { + return descriptor.message.replaceAll(/\{\{\s*(\w+)\s*\}\}/g, (match, key) => descriptor.data?.[key] ?? match); + } + const template = rule.meta?.messages?.[descriptor?.messageId]; + if (typeof template !== 'string') { + return ''; + } + return template.replaceAll(/\{\{\s*(\w+)\s*\}\}/g, (match, key) => descriptor.data?.[key] ?? match); +} + +function withGating(rule, shouldGate) { + return { + ...rule, + create(context) { + const gatedContext = Object.create(context, { + report: { + // Forwarded as-is rather than as one descriptor: ESLint also accepts the legacy + // `report(node, message)` form, and dropping the extra arguments would change the message. + value(...args) { + if (shouldGate(rule, args[0])) { + const filename = context.filename ?? context.getFilename(); + const sourceText = (context.sourceCode ?? context.getSourceCode()).text; + if (isFileMemoizedByBothCompilers(filename, sourceText)) { + return; + } + } + return context.report(...args); + }, + }, + }); + return rule.create(gatedContext); + }, + }; +} + +function withFullGating(rule) { + return withGating(rule, () => true); +} + +function withMessageGating(rule, pattern) { + return withGating(rule, (target, descriptor) => pattern.test(resolveMessage(target, descriptor))); +} + +export {withFullGating, withMessageGating}; diff --git a/config/oxlint/reactCompilerRust.mjs b/config/oxlint/reactCompilerRust.mjs new file mode 100644 index 000000000000..8fe042c6bfa4 --- /dev/null +++ b/config/oxlint/reactCompilerRust.mjs @@ -0,0 +1,236 @@ +import path from 'node:path'; +import {transformSync} from 'oxc-transform-react'; + +const RULE_BY_CATEGORY = { + Refs: 'refs', + EffectSetState: 'set-state-in-effect', + RenderSetState: 'set-state-in-render', + PreserveManualMemo: 'preserve-manual-memoization', + Immutability: 'immutability', + StaticComponents: 'static-components', + UseMemo: 'use-memo', + Globals: 'globals', + ErrorBoundaries: 'error-boundaries', + Purity: 'purity', + IncompatibleLibrary: 'incompatible-library', + UnsupportedSyntax: 'unsupported-syntax', +}; + +// A `Config` diagnostic means the options handed to the compiler are wrong, so it is never +// ignorable: a broken ENVIRONMENT would otherwise become twelve rules that silently report nothing. +const CONFIG_CATEGORY = 'Config'; + +// ESLint does not enable the rule behind each of these, so surfacing them would be oxlint-only +// noise. +const IGNORED_CATEGORIES = new Set([ + 'CapitalizedCalls', + 'EffectDependencies', + 'EffectDerivationsOfState', + 'EffectExhaustiveDependencies', + 'FBT', + 'Gating', + 'Hooks', + 'Invariant', + 'MemoDependencies', + 'Suppression', + 'Syntax', + 'Todo', + 'VoidUseMemo', +]); + +const COMPILATION_SKIPPED_CATEGORIES = new Set(['EffectDependencies', 'IncompatibleLibrary', 'PreserveManualMemo', 'UnsupportedSyntax']); + +const ENVIRONMENT = { + validateRefAccessDuringRender: true, + validateNoSetStateInRender: true, + validateNoSetStateInEffects: true, + // The plugin spells this `validateNoJSXInTryStatements`; oxc spells it with a lowercase `sx`. + validateNoJsxInTryStatements: true, + validateNoImpureFunctionsInRender: true, + validateStaticComponents: true, + validateNoFreezingKnownMutableFunctions: true, + validateNoVoidUseMemo: true, + validateNoCapitalizedCalls: [], + validateHooksUsage: true, + validateNoDerivedComputationsInEffects: true, + enableUseKeyedState: false, + enableVerboseNoSetStateInEffect: false, + validateExhaustiveEffectDependencies: 'off', + enableTreatRefLikeIdentifiersAsRefs: true, +}; + +// Categories are only available inside the formatted `codeframe` string, which is not a public API of +// oxc-transform-react. Hence the exact version pin in package.json and the throw on an unmapped +// category: an upstream rename has to fail loudly. +const CATEGORY_PATTERN = /react-compiler\(([^)]+)\)/; + +const cache = new Map(); + +function getLang(ext) { + if (ext === 'tsx') { + return 'tsx'; + } + if (ext === 'ts') { + return 'ts'; + } + return 'jsx'; +} + +function utf8Length(code) { + if (code < 0x80) { + return 1; + } + if (code < 0x800) { + return 2; + } + // A lone surrogate is three bytes too: it is replaced with U+FFFD on the way into Rust, so the + // source oxc measured is the same width either way. + if (code < 0x10000) { + return 3; + } + return 4; +} + +// `label.start`/`label.end` are UTF-8 *byte* offsets into the source oxc parsed, while a JavaScript +// string is indexed in UTF-16 code units. Indexing `sourceText` with one directly overshoots by one +// unit for every extra byte, which drifts across newlines in any file holding non-ASCII (281 of +// src/'s .tsx files do), so a diagnostic would anchor on the wrong line and an +// `eslint-disable-next-line` on the right line would stop suppressing it. This maps a byte offset +// back to the line and UTF-16 column oxlint's `loc` expects. Built once per file rather than per +// diagnostic, hence the closure. +function buildOffsetToPoint(source) { + // Byte and UTF-16 offset of the start of each line, both indexed by line - 1. + const lineBytes = [0]; + const lineUnits = [0]; + let byte = 0; + for (let unit = 0; unit < source.length; ) { + const code = source.codePointAt(unit); + byte += utf8Length(code); + unit += code > 0xffff ? 2 : 1; + if (code === 0x0a) { + lineBytes.push(byte); + lineUnits.push(unit); + } + } + + return function offsetToPoint(offset) { + // The last line starting at or before `offset`. An offset past the end of the source clamps + // to the final line rather than running off it. + let low = 0; + let high = lineBytes.length - 1; + while (low < high) { + const mid = Math.ceil((low + high) / 2); + if (lineBytes[mid] <= offset) { + low = mid; + } else { + high = mid - 1; + } + } + + // Walked rather than subtracted: a column is UTF-16 code units, not bytes. + let byteInLine = lineBytes[low]; + let unit = lineUnits[low]; + while (byteInLine < offset && unit < source.length) { + const code = source.codePointAt(unit); + byteInLine += utf8Length(code); + unit += code > 0xffff ? 2 : 1; + } + return {line: low + 1, column: unit - lineUnits[low]}; + }; +} + +function buildMessage(category, error) { + const heading = COMPILATION_SKIPPED_CATEGORIES.has(category) ? 'Compilation Skipped' : 'Error'; + const summary = `${heading}: ${error.message ?? 'Unknown compiler error'}`; + return error.helpMessage ? `${summary}\n\n${error.helpMessage}.` : summary; +} + +function analyze(filename, sourceText) { + let result; + try { + result = transformSync(filename, sourceText, { + lang: getLang(path.extname(filename).slice(1)), + reactCompiler: { + target: '19', + outputMode: 'lint', + // Without it the binding returns only fatal diagnostics, so a non-blocking problem + // reports nothing and the rule looks clean. + reportDiagnostics: true, + flowSuppressions: false, + // Without it the compiler skips every function under an + // `eslint-disable-next-line react-hooks/exhaustive-deps` comment, and the repo has + // 228. Lint-only: setting it in the build configs would change what those comments + // make the build memoize, i.e. what ships. + eslintSuppressionRules: [], + environment: ENVIRONMENT, + }, + }); + } catch (error) { + // Malformed options are rejected at the binding rather than reported as a diagnostic; + // swallowing it would make one bad edit to ENVIRONMENT look like a clean repo. + const message = error instanceof Error ? error.message : String(error); + if (/is none of these types|ReactCompilerOptions/.test(message)) { + throw new Error(`oxc-transform-react rejected this module's React Compiler options: ${message}`); + } + return []; + } + + const errors = result.errors ?? []; + + // A config oxc rejects is a bug in this module, not a finding about the file. Fail loudly, the + // same way an unmapped category does. + const rejected = errors.find((error) => (error.message ?? '').startsWith('Invalid React Compiler')); + if (rejected) { + throw new Error(`oxc-transform-react rejected this module's React Compiler options: ${rejected.message}`); + } + + // A fatal result carrying no `react-compiler(...)` category means the compiler never got as far + // as analyzing: a parse failure aborts that way. oxlint's own parser reports the syntax error, so + // a rule here stays quiet rather than throwing on it. + if (result.fatal && !errors.some((error) => CATEGORY_PATTERN.test(error.codeframe ?? ''))) { + return []; + } + + const diagnostics = []; + const offsetToPoint = buildOffsetToPoint(sourceText); + for (const error of errors) { + const match = CATEGORY_PATTERN.exec(error.codeframe ?? ''); + if (!match) { + throw new Error(`React Compiler diagnostic with no category in its codeframe (${filename}): ${error.message ?? ''}`); + } + const category = match[1]; + if (category === CONFIG_CATEGORY) { + throw new Error( + `oxc-transform-react rejected this module's React Compiler options while linting ${filename}: ${error.message ?? ''}. Fix ENVIRONMENT or the reactCompiler options in config/oxlint/reactCompilerRust.mjs.`, + ); + } + if (IGNORED_CATEGORIES.has(category)) { + continue; + } + const ruleName = RULE_BY_CATEGORY[category]; + if (!ruleName) { + throw new Error( + `Unknown React Compiler category '${category}' in ${filename}. oxc-transform-react likely renamed or added one: map it in RULE_BY_CATEGORY or IGNORED_CATEGORIES in config/oxlint/reactCompilerRust.mjs.`, + ); + } + const label = error.labels?.[0]; + diagnostics.push({ + ruleName, + loc: { + start: offsetToPoint(label?.start ?? 0), + end: offsetToPoint(label?.end ?? label?.start ?? 0), + }, + message: buildMessage(category, error), + }); + } + return diagnostics; +} + +function reactCompilerDiagnostics(filename, sourceText) { + if (!cache.has(filename)) { + cache.set(filename, analyze(filename, sourceText)); + } + return cache.get(filename); +} + +export {RULE_BY_CATEGORY, reactCompilerDiagnostics}; diff --git a/config/oxlint/ruleNames.d.mts b/config/oxlint/ruleNames.d.mts new file mode 100644 index 000000000000..21853846a690 --- /dev/null +++ b/config/oxlint/ruleNames.d.mts @@ -0,0 +1,7 @@ +declare const OXLINT_RULE_RENAMES: Record; +declare const HOSTED_RULE_ESLINT_ID: Record; +declare const HOSTED_RULE_ORIGIN: Record; +declare function hostedRuleNames(eslintPrefix: string): string[]; +declare function oxlintCodeToESLintRuleID(code: string): string; + +export {HOSTED_RULE_ESLINT_ID, HOSTED_RULE_ORIGIN, OXLINT_RULE_RENAMES, hostedRuleNames, oxlintCodeToESLintRuleID}; diff --git a/config/oxlint/ruleNames.mjs b/config/oxlint/ruleNames.mjs new file mode 100644 index 000000000000..ad72ded53b59 --- /dev/null +++ b/config/oxlint/ruleNames.mjs @@ -0,0 +1,98 @@ +// Plain .mjs so the bun lint pipeline and oxlint's JS plugin runtime can both read one copy; only the +// first of those can load TypeScript. +const OXLINT_RULE_RENAMES = { + 'no-object-constructor': 'no-new-object', + 'no-new-native-nonconstructor': 'no-new-symbol', +}; + +// A rule hosted through oxlint's `jsPlugins` cannot take a native plugin's prefix (`react`, +// `import`, `react-hooks` and `@typescript-eslint` are reserved), so it is registered bare under +// `hosted` and this table is what gives it back its ESLint identity. +const HOSTED_RULE_ORIGIN = { + 'jsx-no-bind': 'react', + 'function-component-definition': 'react', + 'jsx-no-constructed-context-values': 'react', + 'exhaustive-deps': 'react-hooks', + 'prefer-default-export': 'import', + order: 'import', + 'no-types': 'jsdoc', + 'naming-convention': '@typescript-eslint', + 'no-import-module-exports': 'import', + 'no-relative-packages': 'import', + 'no-useless-path-segments': 'import', + 'default-props-match-prop-types': 'react', + 'forbid-foreign-prop-types': 'react', + 'forbid-prop-types': 'react', + 'jsx-uses-react': 'react', + 'jsx-uses-vars': 'react', + 'no-access-state-in-setstate': 'react', + 'no-arrow-function-lifecycle': 'react', + 'no-deprecated': 'react', + 'no-invalid-html-attribute': 'react', + 'no-typos': 'react', + 'no-unused-class-component-methods': 'react', + 'no-unused-prop-types': 'react', + 'no-unused-state': 'react', + 'prefer-exact-props': 'react', + 'prefer-stateless-function': 'react', + 'sort-comp': 'react', + 'static-property-placement': 'react', + 'require-jsdoc': 'jsdoc', + 'await-interactions': 'storybook', + 'context-in-play-function': 'storybook', + 'default-exports': 'storybook', + 'hierarchy-separator': 'storybook', + 'no-redundant-story-name': 'storybook', + 'no-renderer-packages': 'storybook', + 'no-uninstalled-addons': 'storybook', + 'prefer-pascal-case': 'storybook', + 'story-exports': 'storybook', + 'use-storybook-expect': 'storybook', + 'use-storybook-testing-library': 'storybook', +}; + +// Repo-written hosted rules that report under an existing ESLint rule id. +const HOSTED_RULE_ESLINT_ID = { + 'no-deprecated-self-closing-jsx': '@typescript-eslint/no-deprecated', +}; + +const REACT_HOOKS_RULES_UNDER_REACT = new Set(['exhaustive-deps', 'rules-of-hooks']); + +const OXLINT_DIAGNOSTIC_CODE = /^([\w@/.-]+)\((.+)\)$/; + +function hostedRuleNames(eslintPrefix) { + return Object.keys(HOSTED_RULE_ORIGIN).filter((name) => HOSTED_RULE_ORIGIN[name] === eslintPrefix); +} + +function oxlintCodeToESLintRuleID(code) { + const match = OXLINT_DIAGNOSTIC_CODE.exec(code); + if (!match) { + return code; + } + const [, plugin, rule] = match; + if (plugin === 'eslint' || plugin === 'core') { + return OXLINT_RULE_RENAMES[rule] ?? rule; + } + if (plugin === 'typescript') { + return `@typescript-eslint/${rule}`; + } + if (plugin === 'rc' || (plugin === 'react' && REACT_HOOKS_RULES_UNDER_REACT.has(rule))) { + return `react-hooks/${rule}`; + } + if (plugin === 'hosted') { + if (HOSTED_RULE_ESLINT_ID[rule]) { + return HOSTED_RULE_ESLINT_ID[rule]; + } + const origin = HOSTED_RULE_ORIGIN[rule]; + if (!origin) { + throw new Error(`No HOSTED_RULE_ORIGIN entry for hosted rule "${rule}". Add it to config/oxlint/ruleNames.mjs.`); + } + return `${origin}/${rule}`; + } + if (plugin === 'jsx_a11y') { + return `jsx-a11y/${rule}`; + } + return `${plugin}/${rule}`; +} + +export {HOSTED_RULE_ESLINT_ID, HOSTED_RULE_ORIGIN, OXLINT_RULE_RENAMES, hostedRuleNames, oxlintCodeToESLintRuleID}; diff --git a/config/reactCompiler/suppressedRules.d.mts b/config/reactCompiler/suppressedRules.d.mts new file mode 100644 index 000000000000..21d60d31f411 --- /dev/null +++ b/config/reactCompiler/suppressedRules.d.mts @@ -0,0 +1,4 @@ +declare const RULES_SUPPRESSED_BY_REACT_COMPILER: Set; +declare const EXHAUSTIVE_DEPS_USECALLBACK_USEMEMO_PATTERN: RegExp; + +export {EXHAUSTIVE_DEPS_USECALLBACK_USEMEMO_PATTERN, RULES_SUPPRESSED_BY_REACT_COMPILER}; diff --git a/config/reactCompiler/suppressedRules.mjs b/config/reactCompiler/suppressedRules.mjs new file mode 100644 index 000000000000..6c1664e644d5 --- /dev/null +++ b/config/reactCompiler/suppressedRules.mjs @@ -0,0 +1,8 @@ +// Plain `.mjs`: oxlint's JS plugin runtime cannot load TypeScript. +const RULES_SUPPRESSED_BY_REACT_COMPILER = new Set(['react/jsx-no-constructed-context-values', 'rulesdir/no-inline-useOnyx-selector']); + +// Only the "wrap in useCallback/useMemo" suggestions are false positives in compiled files; +// warnings about genuinely missing dependencies are not suppressed. +const EXHAUSTIVE_DEPS_USECALLBACK_USEMEMO_PATTERN = /\buseCallback\(\) Hook\b|\buseMemo\(\) Hook\b/; + +export {EXHAUSTIVE_DEPS_USECALLBACK_USEMEMO_PATTERN, RULES_SUPPRESSED_BY_REACT_COMPILER}; diff --git a/cspell.json b/cspell.json index 07b8cebe6eb5..496f73e485ff 100644 --- a/cspell.json +++ b/cspell.json @@ -141,6 +141,11 @@ "Erste", "Español", "detekt", + "oxfmtrc", + "oxlint", + "oxlintrc", + "tsgo", + "tsgolint", "Fabrikam", "fillyjonk", "hemulen", @@ -947,6 +952,7 @@ "proguard", "purchaseamount", "purchasecurrency", + "purgeable", "QAPR", "QBWC", "qrcode", @@ -1014,6 +1020,8 @@ "socklen", "syscalls", "serveo", + "nonconstructor", + "setstate", "setuptools", "sharee", "shareeEmail", @@ -1285,6 +1293,8 @@ "tests/unit/hooks/useLetterAvatars.test.tsx", "tests/unit/usePersonalDetailSearchSelectorTest.tsx", "config/eslint/eslint.seatbelt.tsv", + "config/oxlint/oxlint.seatbelt.tsv", + "oxlint.config.mts", "tests/unit/BankAccountUtilsTest.ts", "tests/unit/__snapshots__/ReportAlternateTextParityTest.ts.snap", "evals/proposalPolice/fixtures/**" diff --git a/knip.json b/knip.json index 730d221fe72e..986a13935cf6 100644 --- a/knip.json +++ b/knip.json @@ -82,6 +82,8 @@ "html-entities", "@fullstory/babel-plugin-react-native", "@fullstory/babel-plugin-annotate-react", + "eslint-config-airbnb-typescript", + "eslint-plugin-react-hooks", "eslint-config-prettier", "eslint-plugin-storybook", "@dword-design/eslint-plugin-import-alias", @@ -91,5 +93,8 @@ "diff-so-fancy", "@babel/plugin-proposal-class-properties" ], - "ignoreBinaries": ["metro-symbolicate", "mkcert", "openssl"] + "ignoreBinaries": ["metro-symbolicate", "mkcert", "openssl"], + "ignoreIssues": { + "config/oxlint/ruleNames.mjs": ["exports"] + } } diff --git a/oxlint.config.mts b/oxlint.config.mts new file mode 100644 index 000000000000..b1be3774326b --- /dev/null +++ b/oxlint.config.mts @@ -0,0 +1,1327 @@ +import {defineConfig} from 'oxlint'; + +// Every block that sets `no-restricted-imports` has to restate the whole option object, because an +// oxlint override replaces it rather than merging into it. Building each variant from these keeps the +// copies identical, the way `restrictedImportPaths` does in config/eslint/eslint.config.mjs. +const restrictedImportPaths = [ + { + name: 'react-native', + importNames: [ + 'useWindowDimensions', + 'StatusBar', + 'TouchableOpacity', + 'TouchableWithoutFeedback', + 'TouchableNativeFeedback', + 'TouchableHighlight', + 'Pressable', + 'Text', + 'ScrollView', + 'ActivityIndicator', + 'Animated', + 'findNodeHandle', + 'InteractionManager', + ], + message: + "\nFor 'useWindowDimensions', please use '@src/hooks/useWindowDimensions' instead.\nFor 'TouchableOpacity', 'TouchableWithoutFeedback', 'TouchableNativeFeedback', 'TouchableHighlight', 'Pressable', please use 'PressableWithFeedback' and/or 'PressableWithoutFeedback' from '@components/Pressable' instead.\nFor 'StatusBar', please use '@libs/StatusBar' instead.\nFor 'Text', please use '@components/Text' instead.\nFor 'ScrollView', please use '@components/ScrollView' instead.\nFor 'ActivityIndicator', please use '@components/ActivityIndicator' instead.\nFor 'Animated', please use 'Animated' from 'react-native-reanimated' instead.\nFor 'InteractionManager', please use afterTransition callbacks on Navigation/KeyboardUtils or other alternatives. See contributingGuides/INTERACTION_MANAGER.md.", + }, + { + name: 'react-native-gesture-handler', + importNames: ['TouchableOpacity', 'TouchableWithoutFeedback', 'TouchableNativeFeedback', 'TouchableHighlight'], + message: "Please use 'PressableWithFeedback' and/or 'PressableWithoutFeedback' from '@components/Pressable' instead.", + }, + { + // ESLint carries this under @typescript-eslint/no-restricted-imports, which oxlint does + // not implement; its core rule accepts allowTypeImports, so the path lands in every + // block that re-sets no-restricted-imports, because an oxlint override replaces the + // option object rather than merging into it. + name: 'react-native-onyx/dist/OnyxUtils', + message: + 'OnyxUtils is not a sanctioned way to read Onyx data. Use useOnyx() from @hooks/useOnyx in render paths, or a short-lived Onyx.connectWithoutView() for non-render logic. Type-only imports are still allowed.', + allowTypeImports: true, + }, + {name: 'awesome-phonenumber', importNames: ['parsePhoneNumber'], message: "Please use '@libs/PhoneNumber' instead."}, + { + name: 'react-native-safe-area-context', + importNames: ['useSafeAreaInsets', 'SafeAreaConsumer', 'SafeAreaInsetsContext'], + message: "Please use 'useSafeAreaInsets' from '@src/hooks/useSafeAreaInset' and/or 'SafeAreaConsumer' from '@components/SafeAreaConsumer' instead.", + }, + {name: 'react', importNames: ['CSSProperties'], message: "Please use 'ViewStyle', 'TextStyle', 'ImageStyle' from 'react-native' instead."}, + {name: 'react', importNames: ['forwardRef'], message: 'forwardRef is deprecated. Please use ref as a prop instead. See: contributingGuides/STYLE.md#forwarding-refs'}, + {name: '@styles/index', importNames: ['default', 'defaultStyles'], message: 'Do not import styles directly. Please use the `useThemeStyles` hook instead.'}, + {name: '@styles/utils', importNames: ['default', 'DefaultStyleUtils'], message: 'Do not import StyleUtils directly. Please use the `useStyleUtils` hook instead.'}, + {name: '@styles/theme', importNames: ['default', 'defaultTheme'], message: 'Do not import themes directly. Please use the `useTheme` hook instead.'}, + {name: '@styles/theme/illustrations', message: 'Do not import theme illustrations directly. Please use the `useThemeIllustrations` hook instead.'}, + {name: 'date-fns/locale', message: "Do not import 'date-fns/locale' directly. Please use the submodule import instead, like 'date-fns/locale/en-GB'."}, + { + name: 'expensify-common', + importNames: ['Device', 'ExpensiMark'], + message: + "\nFor 'Device', do not import it directly, it's known to make VSCode's IntelliSense crash. Please import the desired module from `expensify-common/Device` instead.\nFor 'ExpensiMark', please use '@libs/Parser' instead.", + }, + {name: 'lodash/memoize', message: "Please use '@src/libs/memoize' instead."}, + {name: 'lodash', importNames: ['memoize'], message: "Please use '@src/libs/memoize' instead."}, + {name: 'lodash/isEqual', message: "Please use 'deepEqual' from 'fast-equals' instead."}, + {name: 'lodash', importNames: ['isEqual'], message: "Please use 'deepEqual' from 'fast-equals' instead."}, + {name: 'react-native-onyx', importNames: ['useOnyx'], message: "Please use '@hooks/useOnyx' instead."}, + {name: '@src/utils/findNodeHandle', message: "Do not use 'findNodeHandle' as it is no longer supported on web."}, +]; + +const restrictedImportPatterns = [ + {group: ['**/assets/animations/**/*.json'], message: "Do not import animations directly. Please use the '@components/LottieAnimations' import instead."}, + {group: ['@styles/theme/themes/**'], message: 'Do not import themes directly. Please use the `useTheme` hook instead.'}, + {group: ['@styles/utils/**', '!@styles/utils/FontUtils', '!@styles/utils/types'], message: 'Do not import style util functions directly. Please use the `useStyleUtils` hook instead.'}, + {group: ['@styles/theme/illustrations/themes/**'], message: 'Do not import theme illustrations directly. Please use the `useThemeIllustrations` hook instead.'}, +]; + +// TypeScript files also ban three helpers that have safer replacements. +const typescriptRestrictedImportPatterns = [ + ...restrictedImportPatterns, + { + group: ['**/ReportNameUtils', '**/libs/ReportNameUtils'], + importNames: ['computeReportName'], + message: 'Do not import computeReportName. Use getReportName instead, which properly uses derived report attributes.', + }, + { + group: ['**/PolicyUtils', '**/libs/PolicyUtils'], + importNames: ['isPaidGroupPolicy'], + message: + 'isPaidGroupPolicy is billing/paid-only (Collect/Control). For workspace feature gating use isGroupPolicy so free group plans like Submit are not excluded. If this is genuinely a billing/paid-only check, keep it and disable this line with a reason.', + }, + { + group: ['**/ReportUtils', '**/libs/ReportUtils'], + importNames: ['isPaidGroupPolicy', 'isPaidGroupPolicyExpenseReport'], + message: + 'isPaidGroupPolicy / isPaidGroupPolicyExpenseReport are billing/paid-only. For feature gating use isReportInGroupPolicy / isGroupPolicyExpenseReport so Submit workspaces are not excluded. If this is genuinely billing/paid-only, keep it and disable this line with a reason.', + }, +]; + +// `HeaderWithBackButton` is being migrated to the composed `Header` (`@components/Header`). +// Two patterns for what ESLint writes as one: ESLint matches with gitignore semantics, where a bare +// directory name also covers everything under it, and oxlint matches the path exactly. The second +// entry is what catches `.../HeaderWithBackButton/types`. +const restrictedHeaderImportPatterns = [ + {group: ['**/HeaderWithBackButton'], message: 'HeaderWithBackButton is being migrated to the composed Header. Please use `@components/Header` instead for new usages.'}, + {group: ['**/HeaderWithBackButton/**'], message: 'HeaderWithBackButton is being migrated to the composed Header. Please use `@components/Header` instead for new usages.'}, +]; + +// Headless CLI cannot use useTheme; email charts always render with the light theme. +const victoryChartRendererRestrictedImportPaths = restrictedImportPaths.filter((entry) => entry.name !== '@styles/theme'); +const victoryChartRendererRestrictedImportPatterns = typescriptRestrictedImportPatterns.filter((entry) => !entry.group.includes('@styles/theme/themes/**')); + +export default defineConfig({ + plugins: ['typescript', 'import', 'jsx-a11y', 'unicorn', 'react'], + // Aliases are declared here because oxlint reserves `react`, `import` and `jsdoc` for its native + // ports, and a jsPlugin rule id always carries a prefix. + // prefix ESLint uses, so declaring it would only add noise. + jsPlugins: [ + {name: 'rulesdir', specifier: './config/oxlint/plugins/expensify-rules.mjs'}, + {name: 'report-name-utils', specifier: './config/eslint/plugins/eslint-plugin-report-name-utils.mjs'}, + {name: 'core', specifier: './config/oxlint/plugins/core-rules.mjs'}, + {name: 'hosted', specifier: './config/oxlint/plugins/hosted-rules.mjs'}, + {name: 'rc', specifier: './config/oxlint/plugins/rc-rules.mjs'}, + ], + // Categories off: every rule is listed explicitly to mirror eslint.config.mjs. + categories: {correctness: 'off'}, + options: {typeAware: true}, + env: {builtin: true, es2026: true, node: true, browser: true, audioworklet: true, jest: true}, + globals: {__DEV__: 'readonly'}, + settings: { + react: {version: '19.2.3'}, + propWrapperFunctions: ['forbidExtraProps', 'exact', 'Object.freeze'], + progress: {hide: false}, + // `eslint-seatbelt/configure` is off: eslint-seatbelt suppresses through an ESLint processor, + // which oxlint has no concept of. + }, + ignorePatterns: [ + 'oxlint.config.mts', + '!**/.storybook', + '!**/.github', + '.github/actions/**/index.js', + '**/*.config.js', + '**/*.config.mjs', + '**/node_modules/**/*', + '**/dist/**/*', + 'server/**/dist/**', + 'server/victory-chart-renderer/.dev/**', + '.eslint-reports/**/*', + 'android/**/build/**/*', + 'docs/vendor/**/*', + 'docs/assets/**/*', + 'web/gtm.js', + '**/.expo/**/*', + '**/.rock/**/*', + '**/.yalc/**/*', + 'src/libs/SearchParser/searchParser.js', + 'src/libs/SearchParser/autocompleteParser.js', + 'help/_scripts/**/*', + 'modules/ExpensifyNitroUtils/nitrogen/**/*', + 'Mobile-Expensify/**/*', + '**/vendor', + 'modules/group-ib-fp/**/*', + 'web/snippets/gib.js', + 'web/cloudflare-worker/**/*', + 'src/languages/de.ts', + 'src/languages/el.ts', + 'src/languages/es.ts', + 'src/languages/fr.ts', + 'src/languages/it.ts', + 'src/languages/ja.ts', + 'src/languages/nl.ts', + 'src/languages/pl.ts', + 'src/languages/pt-BR.ts', + 'src/languages/zh-hans.ts', + ], + rules: { + 'array-callback-return': ['error', {allowImplicit: true}], + 'block-scoped-var': 'error', + curly: ['error', 'all'], + 'default-case': ['error', {commentPattern: '^no default$'}], + 'default-case-last': 'error', + 'default-param-last': 'error', + eqeqeq: ['error', 'always', {null: 'ignore'}], + 'grouped-accessor-pairs': 'error', + 'guard-for-in': 'error', + 'max-classes-per-file': ['error', 1], + 'no-caller': 'error', + 'no-case-declarations': 'error', + 'no-constructor-return': 'error', + 'no-else-return': ['error', {allowElseIf: false}], + 'no-empty-function': ['error', {allow: ['arrowFunctions', 'functions', 'methods']}], + 'no-empty-pattern': 'error', + 'no-eval': 'error', + 'no-extend-native': 'error', + 'no-extra-bind': 'error', + 'no-extra-label': 'error', + 'no-fallthrough': 'error', + 'no-global-assign': ['error', {exceptions: []}], + 'no-implied-eval': 'error', + 'no-iterator': 'error', + 'no-labels': ['error', {allowLoop: false, allowSwitch: false}], + 'no-lone-blocks': 'error', + 'no-loop-func': 'error', + 'no-multi-str': 'error', + 'no-new': 'error', + 'no-new-func': 'error', + 'no-new-wrappers': 'error', + 'no-nonoctal-decimal-escape': 'error', + 'no-param-reassign': [ + 'error', + {props: true, ignorePropertyModificationsFor: ['acc', 'accumulator', 'e', 'ctx', 'context', 'req', 'request', 'res', 'response', '$scope', 'staticContext']}, + ], + 'no-proto': 'error', + 'no-restricted-properties': [ + 'error', + {object: 'arguments', property: 'callee', message: 'arguments.callee is deprecated'}, + {object: 'global', property: 'isFinite', message: 'Please use Number.isFinite instead'}, + {object: 'self', property: 'isFinite', message: 'Please use Number.isFinite instead'}, + {object: 'window', property: 'isFinite', message: 'Please use Number.isFinite instead'}, + {object: 'global', property: 'isNaN', message: 'Please use Number.isNaN instead'}, + {object: 'self', property: 'isNaN', message: 'Please use Number.isNaN instead'}, + {object: 'window', property: 'isNaN', message: 'Please use Number.isNaN instead'}, + {property: '__defineGetter__', message: 'Please use Object.defineProperty instead.'}, + {property: '__defineSetter__', message: 'Please use Object.defineProperty instead.'}, + {object: 'Math', property: 'pow', message: 'Use the exponentiation operator (**) instead.'}, + ], + 'no-script-url': 'error', + 'no-self-assign': ['error', {props: true}], + 'no-self-compare': 'error', + 'no-sequences': 'error', + 'no-throw-literal': 'error', + 'no-unused-expressions': ['error', {allowShortCircuit: false, allowTernary: false, allowTaggedTemplates: false}], + 'no-unused-labels': 'error', + 'no-useless-catch': 'error', + 'no-useless-concat': 'error', + 'no-useless-escape': 'error', + 'no-useless-return': 'error', + 'no-void': 'error', + 'no-with': 'error', + 'prefer-promise-reject-errors': ['error', {allowEmptyReject: true}], + 'prefer-regex-literals': ['error', {disallowRedundantWrapping: true}], + radix: 'error', + yoda: 'error', + 'for-direction': 'error', + 'getter-return': ['error', {allowImplicit: true}], + 'no-async-promise-executor': 'error', + 'no-await-in-loop': 'error', + 'no-compare-neg-zero': 'error', + 'no-cond-assign': ['error', 'always'], + 'no-console': ['error', {allow: ['debug', 'error']}], + 'no-constant-condition': 'error', + 'no-control-regex': 'error', + 'no-debugger': 'error', + 'no-dupe-else-if': 'error', + 'no-dupe-keys': 'error', + 'no-duplicate-case': 'error', + 'no-empty': 'error', + 'no-empty-character-class': 'error', + 'no-ex-assign': 'error', + 'no-extra-boolean-cast': 'error', + 'no-func-assign': 'error', + 'no-import-assign': 'error', + 'no-inner-declarations': 'error', + 'no-invalid-regexp': 'error', + 'no-irregular-whitespace': 'error', + 'no-loss-of-precision': 'error', + 'no-misleading-character-class': 'error', + 'no-obj-calls': 'error', + 'no-promise-executor-return': 'error', + 'no-prototype-builtins': 'error', + 'no-regex-spaces': 'error', + 'no-setter-return': 'error', + 'no-sparse-arrays': 'error', + 'no-template-curly-in-string': 'error', + 'no-unexpected-multiline': 'error', + 'no-unreachable': 'error', + 'no-unsafe-finally': 'error', + 'no-unsafe-negation': 'error', + 'no-unsafe-optional-chaining': ['error', {disallowArithmeticOperators: true}], + 'no-useless-backreference': 'error', + 'use-isnan': 'error', + 'valid-typeof': ['error', {requireStringLiterals: true}], + 'arrow-body-style': ['error', 'as-needed', {requireReturnForObjectLiteral: false}], + 'constructor-super': 'error', + 'no-class-assign': 'error', + 'no-const-assign': 'error', + 'no-dupe-class-members': 'error', + 'no-restricted-imports': [ + 'error', + { + paths: [ + {name: 'react-native', importNames: ['Button', 'Text', 'TextInput', 'Picker'], message: 'Please use an Expensify component from src/components/ instead.'}, + {name: 'react-native', importNames: ['SafeAreaView'], message: 'Please use SafeAreaView from react-native-safe-area-context'}, + ], + }, + ], + 'no-this-before-super': 'error', + 'no-useless-computed-key': 'error', + 'no-useless-constructor': 'error', + 'no-useless-rename': ['error', {ignoreDestructuring: false, ignoreImport: false, ignoreExport: false}], + 'no-var': 'error', + 'object-shorthand': ['error', 'always', {ignoreConstructors: false, avoidQuotes: true}], + 'prefer-arrow-callback': ['error', {allowNamedFunctions: false, allowUnboundThis: true}], + 'prefer-const': ['error', {destructuring: 'any', ignoreReadBeforeAssign: true}], + 'prefer-numeric-literals': 'error', + 'prefer-rest-params': 'error', + 'prefer-spread': 'error', + 'prefer-template': 'error', + 'require-yield': 'error', + 'symbol-description': 'error', + 'import/no-named-as-default': 'error', + 'import/no-named-as-default-member': 'error', + 'import/no-mutable-exports': 'error', + 'import/no-amd': 'error', + 'import/first': 'error', + 'import/no-duplicates': 'error', + 'import/extensions': ['error', 'ignorePackages', {js: 'never', mjs: 'never', jsx: 'never', ts: 'never', tsx: 'never', cts: 'never', mts: 'never'}], + 'import/newline-after-import': 'error', + // OFF, shadowed not dropped: oxlint's native port anchors its reports on a different line + // than ESLint's, so ESLint's own rule runs instead as `hosted/prefer-default-export`. + 'import/prefer-default-export': 'off', + 'import/no-absolute-path': 'error', + 'import/no-webpack-loader-syntax': 'error', + 'import/no-named-default': 'error', + 'import/no-self-import': 'error', + 'import/no-cycle': 'error', + 'rulesdir/context-provider-split-values': 'error', + 'rulesdir/no-acc-spread-in-reduce': 'error', + 'rulesdir/no-api-in-views': 'error', + 'rulesdir/no-direct-personal-details-list': 'error', + 'rulesdir/no-api-side-effects-method': 'error', + 'rulesdir/no-beta-handler': 'error', + 'rulesdir/no-call-actions-from-actions': 'error', + 'rulesdir/no-deep-equal-in-memo': 'error', + 'rulesdir/no-default-id-values': 'error', + 'rulesdir/no-default-props': 'error', + 'rulesdir/no-direct-pre-insert-fullscreen-under-rhp': 'error', + 'rulesdir/no-inline-named-export': 'error', + 'rulesdir/no-multiple-api-calls': 'error', + 'rulesdir/no-multiple-onyx-in-file': 'error', + 'rulesdir/no-negated-variables': 'error', + 'rulesdir/no-onyx-connect': 'error', + 'rulesdir/no-raw-typography': 'error', + 'rulesdir/no-layout-spacing-conditional': 'error', + 'rulesdir/no-set-or-map-return-in-useOnyx-selector': 'error', + 'rulesdir/no-thenable-actions-in-views': 'error', + 'rulesdir/no-unstable-hook-defaults': 'error', + 'rulesdir/no-use-state-initializer-functions': 'error', + 'rulesdir/no-useless-compose': 'error', + 'rulesdir/prefer-actions-set-data': 'error', + 'rulesdir/prefer-early-return': 'error', + 'rulesdir/prefer-localization': 'error', + 'rulesdir/prefer-narrow-hook-dependencies': ['error', {stableObjectPatterns: ['[Ss]tyles?$', '^theme', '[Ii]cons?$']}], + 'rulesdir/prefer-onyx-connect-in-libs': 'error', + 'rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth': 'error', + 'rulesdir/prefer-type-fest': 'error', + 'rulesdir/require-a11y-disable-justification': 'error', + 'rulesdir/require-live-region-for-status-updates': 'error', + 'rulesdir/require-locale-for-localized-date-format': 'error', + 'rulesdir/use-double-negation-instead-of-boolean': 'error', + // OFF, blocked upstream: needs the type of the `&&` left operand; jsPlugins get no type info. + 'rulesdir/boolean-conditional-rendering': 'off', + // OFF, superseded: needs typeChecker.isArrayType, and jsPlugins get no type info. + // `unicorn/prefer-at` at default options stands in for the x[x.length - N] family. + 'rulesdir/prefer-at': 'off', + // OFF here, ON for src/**/*.ts(x): ESLint's copy needs the type checker, so oxlint runs a + // type-free rewrite (config/oxlint/preferLocaleCompareFromContext.mjs) instead. + 'rulesdir/prefer-locale-compare-from-context': 'off', + // Hosted behind the React Compiler gate (config/oxlint/reactCompilerGate.mjs): ESLint's + // processor drops this rule's messages in a dual-memoized file, and the gate mirrors that. + 'rulesdir/no-inline-useOnyx-selector': 'error', + 'no-array-constructor': 'error', + 'no-bitwise': 'error', + 'no-lonely-if': 'error', + 'no-multi-assign': ['error'], + 'no-nested-ternary': 'error', + 'no-underscore-dangle': ['error', {allow: ['__REDUX_DEVTOOLS_EXTENSION_COMPOSE__'], allowAfterThis: false, allowAfterSuper: false, enforceInMethodNames: true}], + 'no-unneeded-ternary': ['error', {defaultAssignment: false}], + 'operator-assignment': ['error', 'always'], + 'prefer-exponentiation-operator': 'error', + 'prefer-object-spread': 'error', + 'unicorn/prefer-set-has': 'error', + 'unicorn/no-array-for-each': 'error', + 'unicorn/prefer-array-find': 'error', + 'unicorn/prefer-set-size': 'error', + 'unicorn/prefer-string-replace-all': 'error', + // ON with no ESLint counterpart: stands in for `rulesdir/prefer-at`, which needs the type + // checker. Default options only; `checkAllIndexAccess` is what makes it unusable. + 'unicorn/prefer-at': 'error', + 'no-delete-var': 'error', + 'no-label-var': 'error', + 'no-restricted-globals': [ + 'error', + {name: 'isFinite', message: 'Use Number.isFinite instead https://github.com/airbnb/javascript#standard-library--isfinite'}, + {name: 'isNaN', message: 'Use Number.isNaN instead https://github.com/airbnb/javascript#standard-library--isnan'}, + 'addEventListener', + 'blur', + 'close', + 'closed', + 'confirm', + 'defaultStatus', + 'defaultstatus', + 'event', + 'external', + 'find', + 'focus', + 'frameElement', + 'frames', + 'history', + 'innerHeight', + 'innerWidth', + 'length', + 'location', + 'locationbar', + 'menubar', + 'moveBy', + 'moveTo', + 'name', + 'onblur', + 'onerror', + 'onfocus', + 'onload', + 'onresize', + 'onunload', + 'open', + 'opener', + 'opera', + 'outerHeight', + 'outerWidth', + 'pageXOffset', + 'pageYOffset', + 'parent', + 'print', + 'removeEventListener', + 'resizeBy', + 'resizeTo', + 'screen', + 'screenLeft', + 'screenTop', + 'screenX', + 'screenY', + 'scroll', + 'scrollbars', + 'scrollBy', + 'scrollTo', + 'scrollX', + 'scrollY', + 'self', + 'status', + 'statusbar', + 'stop', + 'toolbar', + 'top', + ], + 'no-shadow': 'error', + 'no-shadow-restricted-names': 'error', + 'no-unused-vars': ['error', {vars: 'all', args: 'after-used', ignoreRestSiblings: true}], + 'no-use-before-define': ['error', {functions: true, classes: true, variables: true}], + 'react/jsx-boolean-value': ['error', 'never', {always: []}], + 'react/jsx-no-duplicate-props': 'error', + 'react/jsx-no-undef': 'error', + 'react/jsx-pascal-case': ['error', {allowAllCaps: true, ignore: []}], + 'react/no-danger': 'error', + 'react/no-did-update-set-state': 'error', + 'react/no-will-update-set-state': 'error', + 'react/no-is-mounted': 'error', + 'react/no-string-refs': 'error', + 'react/no-unknown-property': 'error', + 'react/prefer-es6-class': ['error', 'always'], + 'react/self-closing-comp': 'error', + 'react/jsx-no-target-blank': ['error', {enforceDynamicLinks: 'always'}], + // Dead on both tools: after the JS override below only .ts/.mts/.cts stay in scope, and JSX does not parse there. + 'react/jsx-filename-extension': 'off', + 'react/jsx-no-comment-textnodes': 'error', + 'react/no-render-return-value': 'error', + 'react/no-find-dom-node': 'error', + 'react/no-array-index-key': 'error', + 'react/void-dom-elements-no-children': 'error', + 'react/no-redundant-should-component-update': 'error', + 'react/button-has-type': ['error', {button: true, submit: true, reset: false}], + 'react/no-this-in-sfc': 'error', + 'react/no-unsafe': ['error', {checkAliases: true}], + 'react/jsx-fragments': ['error', 'syntax'], + 'react/state-in-constructor': ['error', 'always'], + 'react/jsx-no-script-url': ['error', [{name: 'Link', props: ['to']}]], + 'react/jsx-no-useless-fragment': 'error', + // OFF, shadowed not dropped: oxlint's native port also fires inside Jest mock factories, + // where ESLint's does not. ESLint's own rule runs as `hosted/function-component-definition`. + 'react/function-component-definition': 'off', + // OFF, ESLint's own rule runs as `hosted/jsx-no-constructed-context-values` behind the React + // Compiler gate, which a native Rust rule cannot have. The native port diverges too: it + // anchors on the JSX provider rather than the declaration. + 'react/jsx-no-constructed-context-values': 'off', + 'react/no-unstable-nested-components': 'error', + 'react/no-namespace': 'error', + 'react/style-prop-object': 'error', + 'react/no-unescaped-entities': 'error', + 'react/no-children-prop': 'error', + 'jsx-a11y/alt-text': ['error', {elements: ['img', 'object', 'area', 'input[type="image"]'], img: [], object: [], area: [], 'input[type="image"]': []}], + 'jsx-a11y/anchor-has-content': 'error', + 'jsx-a11y/anchor-is-valid': ['error', {components: ['Link'], specialLink: ['to'], aspects: ['noHref', 'invalidHref', 'preferButton']}], + 'jsx-a11y/aria-activedescendant-has-tabindex': 'error', + 'jsx-a11y/aria-props': 'error', + 'jsx-a11y/aria-proptypes': 'error', + 'jsx-a11y/aria-role': ['error', {ignoreNonDOM: false}], + 'jsx-a11y/aria-unsupported-elements': 'error', + 'jsx-a11y/control-has-associated-label': [ + 'error', + { + labelAttributes: ['label'], + controlComponents: [], + ignoreElements: ['audio', 'canvas', 'embed', 'input', 'textarea', 'tr', 'video'], + ignoreRoles: ['grid', 'listbox', 'menu', 'menubar', 'radiogroup', 'row', 'tablist', 'toolbar', 'tree', 'treegrid'], + depth: 5, + }, + ], + 'jsx-a11y/heading-has-content': ['error', {components: ['']}], + 'jsx-a11y/html-has-lang': 'error', + 'jsx-a11y/iframe-has-title': 'error', + 'jsx-a11y/img-redundant-alt': 'error', + 'jsx-a11y/interactive-supports-focus': 'error', + 'jsx-a11y/label-has-associated-control': ['error', {labelComponents: [], labelAttributes: [], controlComponents: [], assert: 'either', depth: 25}], + 'jsx-a11y/lang': 'error', + 'jsx-a11y/media-has-caption': ['error', {audio: [], video: [], track: []}], + 'jsx-a11y/mouse-events-have-key-events': 'error', + 'jsx-a11y/no-access-key': 'error', + 'jsx-a11y/no-autofocus': ['error', {ignoreNonDOM: true}], + 'jsx-a11y/no-distracting-elements': ['error', {elements: ['marquee', 'blink']}], + 'jsx-a11y/no-interactive-element-to-noninteractive-role': ['error', {tr: ['none', 'presentation']}], + 'jsx-a11y/no-noninteractive-element-interactions': ['error', {handlers: ['onClick', 'onMouseDown', 'onMouseUp', 'onKeyPress', 'onKeyDown', 'onKeyUp']}], + 'jsx-a11y/no-noninteractive-element-to-interactive-role': [ + 'error', + { + ul: ['listbox', 'menu', 'menubar', 'radiogroup', 'tablist', 'tree', 'treegrid'], + ol: ['listbox', 'menu', 'menubar', 'radiogroup', 'tablist', 'tree', 'treegrid'], + li: ['menuitem', 'option', 'row', 'tab', 'treeitem'], + table: ['grid'], + td: ['gridcell'], + }, + ], + 'jsx-a11y/no-noninteractive-tabindex': ['error', {tags: [], roles: ['tabpanel']}], + 'jsx-a11y/no-redundant-roles': 'error', + 'jsx-a11y/no-static-element-interactions': ['error', {handlers: ['onClick', 'onMouseDown', 'onMouseUp', 'onKeyPress', 'onKeyDown', 'onKeyUp']}], + 'jsx-a11y/role-has-required-aria-props': 'error', + 'jsx-a11y/role-supports-aria-props': 'error', + 'jsx-a11y/scope': 'error', + 'jsx-a11y/tabindex-no-positive': 'error', + 'react/rules-of-hooks': 'error', + 'typescript/dot-notation': ['error', {allowKeywords: true}], + 'typescript/no-unsafe-assignment': 'error', + 'typescript/no-unsafe-argument': 'error', + 'typescript/no-unsafe-return': 'error', + 'typescript/no-unsafe-call': 'error', + 'typescript/no-unsafe-member-access': 'error', + 'react/jsx-key': 'error', + 'core/no-restricted-syntax': [ + 'error', + {selector: 'TSEnumDeclaration', message: "Please don't declare enums, use union types instead."}, + { + selector: 'CallExpression[callee.object.name="React"][callee.property.name="forwardRef"]', + message: 'forwardRef is deprecated. Please use ref as a prop instead. See: contributingGuides/STYLE.md#forwarding-refs', + }, + { + selector: 'ImportNamespaceSpecifier[parent.source.value=/^@libs/]', + message: 'Namespace imports from @libs are not allowed. Use named imports instead. Example: import { method } from "@libs/module"', + }, + { + selector: 'ImportNamespaceSpecifier[parent.source.value=/^@userActions/]', + message: 'Namespace imports from @userActions are not allowed. Use named imports instead. Example: import { action } from "@userActions/module"', + }, + { + selector: 'ImportNamespaceSpecifier[parent.source.value=/^\\.\\./]', + message: 'Namespace imports from parent directories are not allowed. Use named imports instead. Example: import { method } from "../libs/module"', + }, + { + selector: 'ImportNamespaceSpecifier[parent.source.value=/^\\./]', + message: 'Namespace imports from sibling modules are not allowed. Use named imports instead. Example: import { method } from "./libs/module"', + }, + { + selector: 'JSXElement[openingElement.name.name=/^Pressable(WithoutFeedback|WithFeedback|WithDelayToggle|WithoutFocus)$/]:not(:has(JSXAttribute[name.name="sentryLabel"]))', + message: 'All Pressable components must include sentryLabel prop for Sentry tracking. Example: ', + }, + {selector: 'LabeledStatement', message: 'Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand.'}, + {selector: 'WithStatement', message: '`with` is disallowed in strict mode because it makes code impossible to predict and optimize. It is also deprecated.'}, + ], + // NOT oxlint's native react/exhaustive-deps: it is a Rust rule, and the React Compiler + // message gate can only wrap a JS plugin. See config/oxlint/plugins/hosted-rules.mjs. + 'hosted/exhaustive-deps': 'error', + 'rc/refs': 'error', + 'rc/set-state-in-effect': 'error', + 'rc/set-state-in-render': 'error', + 'rc/preserve-manual-memoization': 'error', + 'rc/immutability': 'error', + 'rc/static-components': 'error', + 'rc/use-memo': 'error', + 'rc/globals': 'error', + 'rc/error-boundaries': 'error', + 'rc/purity': 'error', + 'rc/incompatible-library': 'error', + 'rc/unsupported-syntax': 'error', + 'no-restricted-exports': ['error', {restrictedNamedExports: ['default', 'then']}], + 'import/export': 'error', + 'react/require-render-return': 'error', + 'core/no-unreachable-loop': ['error', {ignore: []}], + 'hosted/jsx-no-bind': ['error', {ignoreRefs: true, allowArrowFunctions: true, allowFunctions: false, allowBind: false, ignoreDOMComponents: true}], + 'hosted/function-component-definition': ['error', {namedComponents: 'function-declaration', unnamedComponents: 'arrow-function'}], + 'hosted/jsx-no-constructed-context-values': 'error', + 'hosted/prefer-default-export': 'error', + 'hosted/order': ['error', {groups: [['builtin', 'external', 'internal']], distinctGroup: true, sortTypesGroup: false, named: false, warnOnUnassignedImports: false}], + 'hosted/no-types': 'error', + // no-invalid-this is off: ESLint's implementation reaches sourceCode.getJSDocComment, which + // oxlint's JS bridge answers with a throw. + 'core/no-octal': 'error', + 'core/no-octal-escape': 'error', + 'core/no-undef-init': 'error', + 'core/strict': ['error', 'never'], + 'one-var': ['error', 'never'], + // Stands in for ESLint's deprecated `no-new-object`: oxlint only implements the new name, + // which also flags bare `Object()` while not flagging `new Object(arg)`. + 'no-object-constructor': 'error', + 'hosted/no-import-module-exports': ['error', {exceptions: []}], + 'hosted/no-relative-packages': 'error', + 'hosted/no-useless-path-segments': ['error', {commonjs: true}], + 'hosted/default-props-match-prop-types': ['error', {allowRequiredDefaults: false}], + 'hosted/forbid-foreign-prop-types': ['error', {allowInPropTypes: true}], + 'hosted/forbid-prop-types': ['error', {forbid: ['any', 'array', 'object'], checkContextTypes: true, checkChildContextTypes: true}], + 'hosted/jsx-uses-react': 'error', + 'hosted/jsx-uses-vars': 'error', + 'hosted/no-access-state-in-setstate': 'error', + 'hosted/no-arrow-function-lifecycle': 'error', + 'hosted/no-deprecated': 'error', + 'hosted/no-invalid-html-attribute': 'error', + 'hosted/no-typos': 'error', + 'hosted/no-unused-class-component-methods': 'error', + 'hosted/no-unused-prop-types': ['error', {customValidators: [], skipShapeProps: true}], + 'hosted/no-unused-state': 'error', + // Inert on both tools until settings.propWrapperFunctions marks a wrapper `{exact: true}`. + 'hosted/prefer-exact-props': 'error', + 'hosted/prefer-stateless-function': ['error', {ignorePureComponents: true}], + 'hosted/sort-comp': [ + 'error', + { + order: [ + 'static-variables', + 'static-methods', + 'instance-variables', + 'lifecycle', + '/^handle.+$/', + '/^on.+$/', + 'getters', + 'setters', + '/^(get|set)(?!(InitialState$|DefaultProps$|ChildContext$)).+$/', + 'instance-methods', + 'everything-else', + 'rendering', + ], + groups: { + lifecycle: [ + 'displayName', + 'propTypes', + 'contextTypes', + 'childContextTypes', + 'mixins', + 'statics', + 'defaultProps', + 'constructor', + 'getDefaultProps', + 'getInitialState', + 'state', + 'getChildContext', + 'getDerivedStateFromProps', + 'componentWillMount', + 'UNSAFE_componentWillMount', + 'componentDidMount', + 'componentWillReceiveProps', + 'UNSAFE_componentWillReceiveProps', + 'shouldComponentUpdate', + 'componentWillUpdate', + 'UNSAFE_componentWillUpdate', + 'getSnapshotBeforeUpdate', + 'componentDidUpdate', + 'componentDidCatch', + 'componentWillUnmount', + ], + rendering: ['/^render.+$/', 'render'], + }, + }, + ], + 'hosted/static-property-placement': ['error', 'property assignment'], + }, + overrides: [ + { + files: ['**/*.{js,jsx,mjs,cjs}'], + rules: { + 'no-redeclare': ['error', {builtinGlobals: false}], + 'jsdoc/require-param': 'error', + 'jsdoc/require-param-type': 'error', + 'jsdoc/check-tag-names': 'error', + 'import/named': 'error', + 'no-undef': ['error', {typeof: false}], + // Stands in for ESLint's `no-new-symbol`, deprecated in 9.x for this rule. Same + // rename mechanism as no-new-object -> no-object-constructor, see OXLINT_RENAMES. + 'no-new-native-nonconstructor': 'error', + // No native oxlint rule of any name; hosted from ESLint core through `core/`. + 'core/dot-notation': ['error', {allowKeywords: true, allowPattern: ''}], + 'core/no-dupe-args': 'error', + 'core/no-return-await': 'error', + }, + plugins: ['jsdoc'], + }, + { + files: ['**/*.{ts,tsx,cts,mts}'], + rules: { + // Core rules the TypeScript compiler enforces; off as typescript-eslint's eslint-recommended does for TS. + 'no-class-assign': 'off', + 'no-new-native-nonconstructor': 'off', + 'no-with': 'off', + 'typescript/await-thenable': 'error', + 'typescript/ban-ts-comment': 'error', + 'typescript/no-array-delete': 'error', + 'typescript/no-base-to-string': 'error', + 'typescript/no-duplicate-enum-values': 'error', + 'typescript/no-empty-object-type': 'error', + 'typescript/no-extra-non-null-assertion': 'error', + 'typescript/no-for-in-array': 'error', + 'typescript/no-misused-new': 'error', + 'typescript/no-misused-promises': 'error', + 'typescript/no-namespace': 'error', + 'typescript/no-non-null-asserted-optional-chain': 'error', + 'typescript/no-redundant-type-constituents': 'error', + 'typescript/no-this-alias': 'error', + // ON although ESLint reports 0: tsgolint's TS7 inference disagrees with the repo's + // TS 6.0.2 about which assertions are unnecessary. + 'typescript/no-unnecessary-type-assertion': 'error', + 'typescript/no-unnecessary-type-constraint': 'error', + 'typescript/no-unsafe-declaration-merging': 'error', + 'typescript/no-unsafe-enum-comparison': 'error', + 'typescript/no-unsafe-function-type': 'error', + 'typescript/no-unsafe-unary-minus': 'error', + 'typescript/no-wrapper-object-types': 'error', + 'typescript/prefer-as-const': 'error', + 'typescript/prefer-namespace-keyword': 'error', + 'typescript/restrict-plus-operands': 'error', + 'typescript/restrict-template-expressions': 'error', + 'typescript/triple-slash-reference': 'error', + 'typescript/unbound-method': 'error', + // Hosted because tsgolint has not implemented it; oxlint rejects `@typescript-eslint` + // as a JS-plugin name, so it reports under the `hosted/` alias. + 'hosted/naming-convention': [ + 'error', + {selector: ['variable', 'property'], format: null, filter: {regex: '^__esModule$', match: true}}, + {selector: ['variable', 'property'], format: ['camelCase', 'UPPER_CASE', 'PascalCase'], filter: {regex: '^private_[a-z][a-zA-Z0-9]*$', match: false}}, + {selector: 'function', format: ['camelCase', 'PascalCase']}, + {selector: ['typeLike', 'enumMember'], format: ['PascalCase']}, + {selector: ['parameter', 'method'], format: ['camelCase', 'PascalCase'], leadingUnderscore: 'allow'}, + ], + 'core/lines-between-class-members': ['error', 'always', {exceptAfterSingleLine: false}], + // Every "off" in this block mirrors ESLint's resolved config for TS files: TypeScript-covered core rules, + // and typed rules eslint-config-expensify leaves off. Parity, not coverage gaps. + 'typescript/adjacent-overload-signatures': 'error', + 'typescript/array-type': ['error', {default: 'array-simple'}], + 'typescript/ban-tslint-comment': 'error', + 'typescript/class-literal-property-style': 'error', + 'typescript/consistent-generic-constructors': 'error', + 'typescript/consistent-indexed-object-style': 'error', + 'typescript/consistent-type-assertions': 'error', + 'typescript/consistent-type-definitions': ['error', 'type'], + 'typescript/no-confusing-non-null-assertion': 'error', + 'typescript/no-inferrable-types': 'error', + 'typescript/non-nullable-type-assertion-style': 'error', + 'typescript/prefer-for-of': 'error', + 'typescript/prefer-function-type': 'error', + 'typescript/prefer-nullish-coalescing': ['error', {ignoreIfStatements: true, ignorePrimitives: {}, ignoreTernaryTests: true}], + 'typescript/prefer-string-starts-ends-with': 'error', + 'no-implied-eval': 'off', + 'no-new-func': 'off', + 'no-redeclare': ['error', {builtinGlobals: false}], + 'no-unused-vars': ['error', {vars: 'all', args: 'after-used', caughtErrors: 'none', ignoreRestSiblings: true}], + 'no-use-before-define': ['error', {functions: false}], + 'constructor-super': 'off', + 'getter-return': 'off', + 'no-const-assign': 'off', + 'no-dupe-keys': 'off', + 'no-func-assign': 'off', + 'no-import-assign': 'off', + 'no-obj-calls': 'off', + 'no-setter-return': 'off', + 'no-this-before-super': 'off', + 'no-unreachable': 'off', + 'no-unsafe-negation': 'off', + 'valid-typeof': 'off', + 'import/no-named-as-default-member': 'off', + 'jsdoc/require-param': 'off', + 'max-params': ['error', {max: 10}], + 'typescript/no-implied-eval': 'error', + 'typescript/return-await': ['error', 'in-try-catch'], + 'typescript/no-unsafe-type-assertion': 'error', + 'typescript/switch-exhaustiveness-check': ['error', {considerDefaultExhaustiveForUnions: true}], + 'typescript/no-restricted-types': ['error', {types: {object: "Use 'Record' instead."}}], + 'typescript/consistent-type-imports': ['error', {prefer: 'type-imports', fixStyle: 'separate-type-imports'}], + 'typescript/consistent-type-exports': ['error', {fixMixedExportsWithInlineTypeSpecifier: false}], + 'typescript/no-explicit-any': 'error', + 'typescript/no-non-null-assertion': 'error', + 'typescript/prefer-enum-initializers': 'error', + 'typescript/no-import-type-side-effects': 'error', + // Accepted gap: oxlint compares constituents syntactically, so `{a: string} & {a: string}` + // passes where typescript-eslint's type-checker comparison reports it. + 'typescript/no-duplicate-type-constituents': ['error', {ignoreUnions: true}], + }, + plugins: ['jsdoc'], + }, + { + files: ['**/scripts/**', '**/server/**', '**/.github/**'], + rules: { + // CLI output; ESLint resolves no-console off for these directories too. + 'no-console': 'off', + }, + }, + { + files: [ + 'tests/**', + 'test/**', + 'spec/**', + '**/__tests__/**', + '**/__mocks__/**', + 'test.{js,jsx}', + 'test-*.{js,jsx}', + '**/*.{test,spec}.{js,jsx,mjs,cjs}', + '**/*.{test,spec}.{ts,tsx,cts,mts}', + '**/*_test.{js,jsx,ts,tsx}', + '**/jest.config.{js,ts,mts}', + '**/jest.setup.{js,ts}', + 'jestSetup.js', + ], + rules: { + // Mirrors ESLint's test-file block. + 'no-console': 'off', + 'no-await-in-loop': 'off', + 'no-import-assign': 'off', + 'jsdoc/check-tag-names': ['error', {definedTags: ['jest-environment']}], + }, + plugins: ['jsdoc'], + }, + { + files: ['**/*.{ts,tsx,js,jsx,mjs,cjs}'], + rules: { + // Every "off" in this block is off in eslint.config.mjs for the same files. + 'react-native-a11y/has-accessibility-props': 'error', + 'react-native-a11y/has-valid-accessibility-actions': 'error', + 'react-native-a11y/has-valid-accessibility-component-type': 'error', + 'react-native-a11y/has-valid-accessibility-descriptors': ['error', {touchables: ['PressableWithoutFeedback', 'PressableWithFeedback']}], + 'react-native-a11y/has-valid-accessibility-role': 'error', + 'react-native-a11y/has-valid-accessibility-state': 'error', + 'react-native-a11y/has-valid-accessibility-states': 'error', + 'react-native-a11y/has-valid-accessibility-traits': 'error', + 'react-native-a11y/has-valid-accessibility-value': 'error', + 'react-native-a11y/no-nested-touchables': 'error', + 'react-native-a11y/has-valid-accessibility-ignores-invert-colors': 'error', + 'react-native-a11y/has-valid-accessibility-live-region': 'error', + 'react-native-a11y/has-valid-important-for-accessibility': 'error', + '@dword-design/import-alias/prefer-alias': [ + 'error', + { + alias: { + '@assets': './assets', + '@components': './src/components', + '@hooks': './src/hooks', + '@userActions': './src/libs/actions', + '@libs': './src/libs', + '@navigation': './src/libs/Navigation', + '@pages': './src/pages', + '@prompts': './prompts', + '@styles': './src/styles', + '@src': './src', + '@github': './.github', + }, + }, + ], + 'no-unexpected-multiline': 'off', + 'arrow-body-style': 'off', + 'no-empty': ['error', {allowEmptyCatch: true}], + 'import/consistent-type-specifier-style': ['error', 'prefer-top-level'], + 'react/forbid-component-props': [ + 'error', + { + forbid: [ + { + propName: 'fsClass', + allowedFor: ['View', 'Animated.View', 'Text', 'Pressable'], + message: + "The 'fsClass' prop doesn't work for custom components, only RN's View, Text and Pressable.\nPlease use the 'ForwardedFSClassProps' or 'MultipleFSClassProps' types to pass down the desired 'fsClass' value to the allowed components.", + }, + ], + }, + ], + 'no-restricted-properties': [ + 'error', + {object: 'Image', property: 'getSize', message: 'Usage of Image.getSize is restricted. Please use the `react-native-image-size`.'}, + {object: 'HybridAppModule', property: 'isHybridApp', message: 'Use CONFIG.IS_HYBRID_APP instead.'}, + {object: 'HybridAppModule', property: 'closeReactNativeApp', message: 'Use `closeReactNativeApp` from `@userActions/HybridApp` instead.'}, + ], + 'no-restricted-imports': ['error', {paths: restrictedImportPaths, patterns: restrictedImportPatterns}], + 'lodash/import-scope': ['error', 'method'], + 'prefer-regex-literals': 'off', + 'jsdoc/require-param': 'off', + 'jsdoc/require-param-type': 'off', + 'jsdoc/check-tag-names': 'off', + }, + jsPlugins: ['eslint-plugin-react-native-a11y', '@dword-design/eslint-plugin-import-alias', 'eslint-plugin-lodash'], + plugins: ['jsdoc'], + }, + { + files: ['**/*.{js,jsx,mjs,cjs}'], + rules: { + // Mirrors ESLint's plain-JS block. + 'prefer-arrow-callback': 'off', + 'lodash/import-scope': 'off', + }, + jsPlugins: ['eslint-plugin-lodash'], + }, + { + files: ['**/*.{mjs,cjs}', '.storybook/**/*.{ts,tsx}', 'config/rsbuild/**/*.ts'], + rules: { + // Node ESM, Storybook and the Rsbuild config need explicit extensions on relative imports; off in ESLint too. + 'import/extensions': 'off', + }, + }, + { + files: ['**/*.{ts,tsx}'], + rules: { + 'you-dont-need-lodash-underscore/concat': 'error', + 'you-dont-need-lodash-underscore/drop': 'error', + 'you-dont-need-lodash-underscore/drop-right': 'error', + 'you-dont-need-lodash-underscore/index-of': 'error', + 'you-dont-need-lodash-underscore/join': 'error', + 'you-dont-need-lodash-underscore/last': 'error', + 'you-dont-need-lodash-underscore/last-index-of': 'error', + 'you-dont-need-lodash-underscore/reverse': 'error', + 'you-dont-need-lodash-underscore/fill': 'error', + 'you-dont-need-lodash-underscore/find': 'error', + 'you-dont-need-lodash-underscore/detect': 'error', + 'you-dont-need-lodash-underscore/first': 'error', + 'you-dont-need-lodash-underscore/find-index': 'error', + 'you-dont-need-lodash-underscore/is-array': 'error', + 'you-dont-need-lodash-underscore/each': 'error', + 'you-dont-need-lodash-underscore/for-each': 'error', + 'you-dont-need-lodash-underscore/every': 'error', + 'you-dont-need-lodash-underscore/all': 'error', + 'you-dont-need-lodash-underscore/filter': 'error', + 'you-dont-need-lodash-underscore/select': 'error', + 'you-dont-need-lodash-underscore/map': 'error', + 'you-dont-need-lodash-underscore/collect': 'error', + 'you-dont-need-lodash-underscore/reduce': 'error', + 'you-dont-need-lodash-underscore/inject': 'error', + 'you-dont-need-lodash-underscore/foldl': 'error', + 'you-dont-need-lodash-underscore/reduce-right': 'error', + 'you-dont-need-lodash-underscore/foldr': 'error', + 'you-dont-need-lodash-underscore/size': 'error', + 'you-dont-need-lodash-underscore/some': 'error', + 'you-dont-need-lodash-underscore/any': 'error', + 'you-dont-need-lodash-underscore/includes': 'error', + 'you-dont-need-lodash-underscore/contains': 'error', + 'you-dont-need-lodash-underscore/slice': 'error', + 'you-dont-need-lodash-underscore/take-right': 'error', + 'you-dont-need-lodash-underscore/bind': 'error', + 'you-dont-need-lodash-underscore/is-finite': 'error', + 'you-dont-need-lodash-underscore/is-integer': 'error', + 'you-dont-need-lodash-underscore/is-nan': 'error', + 'you-dont-need-lodash-underscore/is-nil': 'error', + 'you-dont-need-lodash-underscore/is-null': 'error', + 'you-dont-need-lodash-underscore/is-undefined': 'error', + 'you-dont-need-lodash-underscore/keys': 'error', + 'you-dont-need-lodash-underscore/extend-own': 'error', + 'you-dont-need-lodash-underscore/assign': 'error', + 'you-dont-need-lodash-underscore/values': 'error', + 'you-dont-need-lodash-underscore/entries': 'error', + 'you-dont-need-lodash-underscore/to-pairs': 'error', + 'you-dont-need-lodash-underscore/pairs': 'error', + 'you-dont-need-lodash-underscore/get': 'error', + 'you-dont-need-lodash-underscore/split': 'error', + 'you-dont-need-lodash-underscore/starts-with': 'error', + 'you-dont-need-lodash-underscore/ends-with': 'error', + 'you-dont-need-lodash-underscore/to-lower': 'error', + 'you-dont-need-lodash-underscore/to-upper': 'error', + 'you-dont-need-lodash-underscore/trim': 'error', + 'you-dont-need-lodash-underscore/pad-start': 'error', + 'you-dont-need-lodash-underscore/pad-end': 'error', + 'you-dont-need-lodash-underscore/repeat': 'error', + 'you-dont-need-lodash-underscore/uniq': 'error', + 'you-dont-need-lodash-underscore/replace': 'error', + 'you-dont-need-lodash-underscore/omit': 'error', + 'you-dont-need-lodash-underscore/flatten': 'error', + // throttle and clone-deep stay off as in ESLint; structuredClone, the suggested clone-deep replacement, is + // unsupported in Hermes (facebook/hermes#68). + 'you-dont-need-lodash-underscore/throttle': 'off', + 'you-dont-need-lodash-underscore/is-string': 'error', + 'you-dont-need-lodash-underscore/cast-array': 'error', + 'you-dont-need-lodash-underscore/clone-deep': 'off', + 'you-dont-need-lodash-underscore/is-function': 'error', + 'you-dont-need-lodash-underscore/union-by': 'error', + 'you-dont-need-lodash-underscore/capitalize': 'error', + 'you-dont-need-lodash-underscore/is-date': 'error', + 'you-dont-need-lodash-underscore/defaults': 'error', + 'you-dont-need-lodash-underscore/is-array-buffer': 'error', + 'you-dont-need-lodash-underscore/head': 'error', + }, + jsPlugins: ['eslint-plugin-you-dont-need-lodash-underscore'], + }, + { + files: ['.github/**/*', 'scripts/**/*', 'server/**/*', 'tests/**/*'], + rules: { + // Sequential awaits are fine in scripts and tests; off in eslint.config.mjs for the same directories. + 'no-await-in-loop': 'off', + 'core/no-restricted-syntax': ['error', 'ForInStatement', 'LabeledStatement', 'WithStatement'], + }, + }, + { + files: ['.github/**/*'], + rules: { + 'no-restricted-imports': ['error', {patterns: [{group: ['@src/**'], message: 'Do not import files from src/ directory as they can break the GH Actions build script.'}]}], + }, + }, + { + // An oxlint override replaces the rule value rather than merging, so the airbnb browser + // list from the root is deliberately not repeated here. + files: ['.github/actions/**/*.ts', '.github/libs/**/*.ts'], + rules: { + 'no-restricted-globals': [ + 'error', + {name: 'module', message: 'This file is bundled as ESM and runs on Node 24. For an entry-point guard use `import.meta.main` instead of `require.main === module`.'}, + {name: '__dirname', message: 'This file is bundled as ESM. Use `import.meta.dirname` instead of `__dirname`.'}, + {name: '__filename', message: 'This file is bundled as ESM. Use `import.meta.filename` instead of `__filename`.'}, + ], + }, + }, + { + files: ['tests/**/*'], + rules: { + // Mirrors ESLint's tests block. + 'import/extensions': 'off', + 'no-import-assign': 'off', + '@dword-design/import-alias/prefer-alias': ['off'], + 'testing-library/await-async-queries': 'error', + 'testing-library/await-async-utils': 'error', + 'testing-library/no-debugging-utils': 'error', + 'testing-library/no-manual-cleanup': 'error', + 'testing-library/no-unnecessary-act': 'error', + 'testing-library/prefer-find-by': 'error', + 'testing-library/prefer-presence-queries': 'error', + 'testing-library/prefer-screen-queries': 'error', + }, + jsPlugins: ['@dword-design/eslint-plugin-import-alias', 'eslint-plugin-testing-library'], + }, + { + files: ['**/*.{ts,tsx}'], + rules: { + 'no-restricted-imports': ['error', {paths: restrictedImportPaths, patterns: [...typescriptRestrictedImportPatterns, ...restrictedHeaderImportPatterns]}], + // Overrides are last-wins, so this block's position is load-bearing twice: this rule + // has to stay above the 83-file block near the end of `overrides` that switches it + // off, and `no-restricted-imports` above has to stay above the two blocks that re-set + // it for 24 files between them. + 'typescript/no-deprecated': ['error', {allow: ['translateFn']}], + // tsgolint misses ``; see config/oxlint/noDeprecatedSelfClosingJsx.mjs. + 'hosted/no-deprecated-self-closing-jsx': [ + 'error', + { + components: [ + { + module: 'src/components/MenuItem', + importName: 'default', + reason: 'Compose `MenuItem.Root` with its leaves, or use a preset from `@components/MenuItem/presets/`, for new usages.', + }, + ], + }, + ], + }, + }, + {files: ['src/**/*'], rules: {'max-lines': ['error', 4000]}}, + { + files: ['modules/ExpensifyNitroUtils/src/**/*'], + rules: { + // Off in eslint.config.mjs for the same files. + 'typescript/consistent-type-definitions': 'off', + }, + }, + { + files: ['server/victory-chart-renderer/**/*.{ts,tsx}'], + rules: {'no-restricted-imports': ['error', {paths: victoryChartRendererRestrictedImportPaths, patterns: victoryChartRendererRestrictedImportPatterns}]}, + }, + {files: ['src/**/*.{ts,tsx}'], rules: {'rulesdir/no-object-keys-includes': 'error', 'rulesdir/prefer-locale-compare-from-context': 'error'}}, + { + files: ['src/styles/typography.ts', 'src/styles/variables.ts'], + rules: { + // The typography token files are where raw font sizes and line heights are defined. + 'rulesdir/no-raw-typography': 'off', + }, + }, + {files: ['src/styles/**'], excludeFiles: ['src/styles/typography.ts', 'src/styles/variables.ts'], rules: {'rulesdir/no-raw-typography': ['error', {allowVariablesReferences: true}]}}, + {files: ['**/en.ts', '**/es.ts'], rules: {'rulesdir/use-periods-for-error-messages': 'error'}}, + {files: ['src/libs/ReportNameUtils.ts'], rules: {'report-name-utils/no-function-call-in-get-report-name': 'error'}}, + // reportAttributes.ts computes report names, so it keeps the base list without the TypeScript-only bans. + { + files: ['src/libs/actions/OnyxDerived/configs/reportAttributes.ts'], + rules: {'no-restricted-imports': ['error', {paths: restrictedImportPaths, patterns: restrictedImportPatterns}]}, + }, + { + files: ['src/languages/**', 'src/CONST/index.ts', 'src/NAICS.ts'], + rules: { + // ESLint applies max-lines to src/** with these files in its ignores list. + 'max-lines': 'off', + }, + }, + { + files: ['scripts/**', '.github/**'], + rules: { + // Deliberate difference, not a mirror: tsgolint's no-unsafe-* port is stricter than typescript-eslint's and this + // loosely typed Actions glue is where it shows. Removing this block adds 695 findings ESLint reports 0 of. + 'typescript/no-unsafe-assignment': 'off', + 'typescript/no-unsafe-argument': 'off', + 'typescript/no-unsafe-return': 'off', + 'typescript/no-unsafe-call': 'off', + 'typescript/no-unsafe-member-access': 'off', + 'no-throw-literal': 'off', + }, + }, + { + files: ['src/libs/Navigation/types.ts', 'src/libs/Navigation/paramLists/**/*.ts'], + rules: { + 'core/no-restricted-syntax': [ + 'error', + { + selector: 'TSPropertySignature[key.name="backTo"]', + message: + 'The `backTo` route param is deprecated. Do not add new `backTo` properties to screen param lists. Please look into the `How to remove backTo from URL` section in contributingGuides/NAVIGATION.md. and use alternative routing methods instead.', + }, + ], + }, + }, + { + files: [ + '.github/actions/javascript/checkAndroidStatus/checkAndroidStatus.ts', + '.github/actions/javascript/getAndroidRolloutPercentage/getAndroidRolloutPercentage.ts', + '__mocks__/reportData/actions.ts', + 'config/rsbuild/rsbuild.common.ts', + 'src/hooks/useAccessibilityAnnouncement/index.ts', + 'src/libs/Parser.ts', + 'src/libs/actions/Chronos.ts', + 'src/libs/actions/IOU/DeleteMoneyRequest.ts', + 'src/libs/actions/IOU/Duplicate.ts', + 'src/libs/actions/IOU/Hold.ts', + 'src/libs/actions/IOU/RejectMoneyRequest.ts', + 'src/libs/actions/IOU/TrackExpense.ts', + 'src/libs/actions/IOU/UpdateMoneyRequest.ts', + 'src/libs/actions/Policy/Member.ts', + 'src/libs/actions/Transaction.ts', + 'src/pages/inbox/conciergeDraftState.ts', + 'src/pages/inbox/report/ReportActionsListItemRenderer.tsx', + 'src/utils/createDummySharedValue.ts', + 'tests/actions/IOU/CreateDraftTransactionTest.ts', + 'tests/actions/IOU/RequestMoneyTest.ts', + 'tests/actions/IOUTest/BulkEditTest.ts', + 'tests/actions/IOUTest/DeleteMoneyRequestTest.ts', + 'tests/actions/IOUTest/DuplicateTest.ts', + 'tests/actions/IOUTest/PayMoneyRequestTest.ts', + 'tests/actions/IOUTest/SplitTest.ts', + 'tests/actions/IOUTest/TrackExpenseTest.ts', + 'tests/actions/IOUTest/UpdateMoneyRequestTest.ts', + 'tests/actions/MergeTransactionTest.ts', + 'tests/actions/ReplaceOptimisticReportWithActualReportTest.ts', + 'tests/actions/ReportTest.ts', + 'tests/actions/TransactionTest.ts', + 'tests/ui/ChatActionableButtonsTest.tsx', + 'tests/ui/DynamicReportDetailsPageTest.tsx', + 'tests/ui/LHNItemsPresence.tsx', + 'tests/ui/MoneyRequestReportViewTest.tsx', + 'tests/ui/RecentlyAddedSectionTest.tsx', + 'tests/ui/ReportActionItemTest.tsx', + 'tests/ui/ReportActionsListTest.tsx', + 'tests/ui/ReportAttachments.tsx', + 'tests/ui/components/BaseReportActionContextMenuTest.tsx', + 'tests/ui/components/LHNOptionsListTest.tsx', + 'tests/unit/AgentRuleChangeLogUtilsTest.ts', + 'tests/unit/DebugUtilsTest.ts', + 'tests/unit/DeleteReportTest.ts', + 'tests/unit/LHNAvatarPipelineTest.ts', + 'tests/unit/MoneyRequestReportUtilsTest.ts', + 'tests/unit/NextStepUtilsTest.ts', + 'tests/unit/OnyxDerived/computeForReportTest.ts', + 'tests/unit/OnyxDerivedTest.tsx', + 'tests/unit/OptionsListUtilsTest.tsx', + 'tests/unit/PolicyChangeLogContentTest.tsx', + 'tests/unit/ReportActionsUtilsTest.ts', + 'tests/unit/ReportPrimaryActionUtilsTest.ts', + 'tests/unit/ReportSecondaryActionUtilsTest.ts', + 'tests/unit/ReportUtilsTest.ts', + 'tests/unit/Search/SearchUIUtilsTest.ts', + 'tests/unit/SidebarUtilsTest.ts', + 'tests/unit/SpendRuleChangeLogUtilsTest.ts', + 'tests/unit/TransactionTest.ts', + 'tests/unit/WhisperContentMentionContextTest.tsx', + 'tests/unit/components/MoneyReportTransactionThreadContext.test.tsx', + 'tests/unit/getReportPreviewSenderIDTest.ts', + 'tests/unit/getSendMessageListWeightTest.ts', + 'tests/unit/getSendMessageSourceTest.ts', + 'tests/unit/hooks/useDeleteTransactionsInvoiceOfflineTest.tsx', + 'tests/unit/hooks/useExpenseActionsInvoiceDeleteTest.tsx', + 'tests/unit/hooks/useExpenseSubmission.test.ts', + 'tests/unit/hooks/useOriginalReportID.test.tsx', + 'tests/unit/hooks/useSelectedTransactionsActions.test.ts', + 'tests/unit/hooks/useShowNotFoundPageInIOUStep.test.ts', + 'tests/unit/hooks/useTransactionThreadReportID.test.ts', + 'tests/unit/inlineEditing/TransactionInlineEdit.test.ts', + 'tests/unit/isConsecutiveActionMadeByPreviousActorTest.ts', + 'tests/unit/libs/MoneyRequestReportUtils.ts', + 'tests/unit/pages/inbox/ConciergeDraftContext.test.tsx', + 'tests/unit/selectors/ReportActionTest.ts', + 'tests/unit/useGetExpensifyCardFromReportActionTest.ts', + 'tests/unit/useOneTransactionThreadReportIDTest.ts', + 'tests/unit/useReportActionsVisibilityTest.tsx', + 'tests/utils/LHNTestUtils.tsx', + 'tests/utils/ReportTestUtils.ts', + 'tests/utils/collections/reportActions.ts', + 'tests/utils/createSharedValueMock.ts', + ], + rules: { + // tsgolint reports writes to deprecated properties, typescript-eslint does not (typescript-eslint#10643); off + // where that would flood. Costs 5 read-site findings ESLint reports in these files. + 'typescript/no-deprecated': 'off', + 'hosted/no-deprecated-self-closing-jsx': 'off', + }, + }, + { + files: ['**/*.{js,jsx,mjs,cjs}'], + rules: { + // Typed rules off for plain JS, as typescript-eslint does; parity, not coverage gaps. + 'typescript/await-thenable': 'off', + 'typescript/consistent-type-exports': 'off', + 'typescript/dot-notation': 'off', + 'typescript/no-array-delete': 'off', + 'typescript/no-base-to-string': 'off', + 'typescript/no-deprecated': 'off', + 'typescript/no-duplicate-type-constituents': 'off', + 'typescript/no-for-in-array': 'off', + 'typescript/no-implied-eval': 'off', + 'typescript/no-misused-promises': 'off', + 'typescript/no-redundant-type-constituents': 'off', + 'typescript/no-unnecessary-type-assertion': 'off', + 'typescript/no-unsafe-enum-comparison': 'off', + 'typescript/no-unsafe-type-assertion': 'off', + 'typescript/no-unsafe-unary-minus': 'off', + 'typescript/non-nullable-type-assertion-style': 'off', + 'typescript/prefer-nullish-coalescing': 'off', + 'typescript/prefer-string-starts-ends-with': 'off', + 'typescript/restrict-plus-operands': 'off', + 'typescript/restrict-template-expressions': 'off', + 'typescript/return-await': 'off', + 'typescript/switch-exhaustiveness-check': 'off', + 'typescript/unbound-method': 'off', + 'typescript/no-unsafe-assignment': 'off', + 'typescript/no-unsafe-argument': 'off', + 'typescript/no-unsafe-return': 'off', + 'typescript/no-unsafe-call': 'off', + 'typescript/no-unsafe-member-access': 'off', + 'rulesdir/no-default-props': 'off', + 'hosted/no-types': 'off', + }, + }, + { + files: ['**/*.d.{ts,tsx}'], + rules: { + // ESLint's code-path analyzer throws on declaration files under oxlint's plugin bridge + // (`Cannot read properties of undefined (reading 'parent')`), and a thrown plugin fails the run. + 'core/no-unreachable-loop': 'off', + }, + }, + { + // Mirrors config/eslint/eslint.config.mjs: bun-types declares + // `expect(...).resolves` / `.rejects` as returning `void` even though Bun's own docs + // recommend (and its runtime requires) awaiting them, so the rule reports every correct + // use of that pattern here. See https://github.com/oven-sh/bun/pull/23425. Without this + // block oxlint reports 30 findings across 7 files that ESLint never shows. + files: ['tests/tooling/**/*.ts'], + rules: {'typescript/await-thenable': 'off'}, + }, + { + // oxlint has no storybook plugin, so the story-file scope `plugin:storybook/recommended` + // applies internally is restated here, with the upstream severities. + files: ['**/*.{stories,story}.{ts,tsx,js,jsx,mjs,cjs}'], + rules: { + // The storybook preset turns this rule off for story globs; oxlint enabled it + // everywhere, so this mirrors the preset's scope. + 'react/rules-of-hooks': 'off', + 'hosted/await-interactions': 'error', + 'hosted/context-in-play-function': 'error', + 'hosted/default-exports': 'error', + 'hosted/hierarchy-separator': 'warn', + 'hosted/no-redundant-story-name': 'warn', + 'hosted/no-renderer-packages': 'error', + 'hosted/prefer-pascal-case': 'warn', + 'hosted/story-exports': 'error', + 'hosted/use-storybook-expect': 'error', + 'hosted/use-storybook-testing-library': 'error', + }, + }, + { + // The one storybook rule upstream scopes to the config file rather than to stories: it + // reads the addon list off disk and checks each one is installed. + files: ['.storybook/main.{js,cjs,mjs,ts}'], + rules: {'hosted/no-uninstalled-addons': 'error'}, + }, + {files: ['src/types/onyx/**/*.ts'], rules: {'hosted/require-jsdoc': ['error', {contexts: ['TSInterfaceDeclaration', 'TSTypeAliasDeclaration']}]}}, + { + // Mirrors eslint.config.mjs: the personal-details wrappers, the key declaration and test setup are exempt. + files: [ + 'src/ONYXKEYS.ts', + 'src/hooks/usePersonalDetails.ts', + 'src/libs/PersonalDetailsStore.ts', + 'src/libs/PersonalDetailsUtils.ts', + 'src/components/OnyxListItemProvider.tsx', + 'src/libs/ExportOnyxState/masking.ts', + 'tests/**/*.{ts,tsx}', + 'jest/**/*.{ts,tsx}', + '__mocks__/**/*.{ts,tsx}', + 'src/**/__mocks__/**/*.{ts,tsx}', + ], + rules: {'rulesdir/no-direct-personal-details-list': 'off'}, + }, + ], +}); diff --git a/package-lock.json b/package-lock.json index 6cca7f501815..e4f160c81406 100644 --- a/package-lock.json +++ b/package-lock.json @@ -272,8 +272,10 @@ "nitrogen": "0.37.1", "onchange": "^7.1.0", "openai": "^7.0.0", - "oxc-transform-react": "0.148.0", + "oxc-transform-react": "0.155.0", "oxfmt": "^0.55.0", + "oxlint": "1.89.0", + "oxlint-tsgolint": "7.0.2003", "patch-package": "^8.1.0-canary.1", "peggy": "^4.0.3", "portfinder": "^1.0.34", @@ -13237,9 +13239,9 @@ ] }, "node_modules/@oxc-transform-react/binding-android-arm-eabi": { - "version": "0.148.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-android-arm-eabi/-/binding-android-arm-eabi-0.148.0.tgz", - "integrity": "sha512-W+elZaL7gMDaRC6OtkYO2gOelNNcCDBO7EAZYGSkPW2WXoCTkILVibnpDkiMFlwBnKQRwqdq7SPk/KwwFyMtPQ==", + "version": "0.155.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-android-arm-eabi/-/binding-android-arm-eabi-0.155.0.tgz", + "integrity": "sha512-X0gdaAcnVLdTUduUdsJ7pRPDpTwhiHRcq8wOC37HXMA1L5XaHbu/zQkeCmb84CChiIvRiQqHF6WkO8DLl62yeA==", "cpu": [ "arm" ], @@ -13254,9 +13256,9 @@ } }, "node_modules/@oxc-transform-react/binding-android-arm64": { - "version": "0.148.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-android-arm64/-/binding-android-arm64-0.148.0.tgz", - "integrity": "sha512-qHDAUwJOfsIAL6s3smScVtCVbkrKYAF2GhNSb2jB3fYvdbaIk96W0BSXvBoye7eee8sIVTjznrb0fSYZxmmdGQ==", + "version": "0.155.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-android-arm64/-/binding-android-arm64-0.155.0.tgz", + "integrity": "sha512-OtYUiF+jniic0JVMoyZPABnSckQUjDJwIyawDCjq8dM5f0bTDvURKgbDJrL7tR+Z86xvdxkvzwtqb0Xb+HlrBA==", "cpu": [ "arm64" ], @@ -13271,9 +13273,9 @@ } }, "node_modules/@oxc-transform-react/binding-darwin-arm64": { - "version": "0.148.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-darwin-arm64/-/binding-darwin-arm64-0.148.0.tgz", - "integrity": "sha512-vkhY+l8u8A3+hfsxQMK+xP7ejiKWR5hbRsnDbg5fNa4tCNNzpeJ8xqmlZ6+MKGhXMP03ZtjXlXGyRaGgCFoAgQ==", + "version": "0.155.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-darwin-arm64/-/binding-darwin-arm64-0.155.0.tgz", + "integrity": "sha512-6EjLyB0XcCaXDWS8/q7QfMdmEGkeI11L9dtKu7Ju3Pl0dRKLcwXVdzdjGP3itzHy4RDbEAuVlCwO6lfFRpkiWg==", "cpu": [ "arm64" ], @@ -13288,9 +13290,9 @@ } }, "node_modules/@oxc-transform-react/binding-darwin-x64": { - "version": "0.148.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-darwin-x64/-/binding-darwin-x64-0.148.0.tgz", - "integrity": "sha512-Ehu33kg2NocBwrVxtlLkGpOzWhzFXRwo7CYxpLFIgmQT24iMYzjgsDjRUW9tHgC1q12UF1A7mUGhZV0wO2sUQQ==", + "version": "0.155.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-darwin-x64/-/binding-darwin-x64-0.155.0.tgz", + "integrity": "sha512-n80yhAy3DNm3u5OqJDpNf+S7gaNpWyICSCoiTt6+oO9YC9oWkm/ywc12Qwrg7wCFtvniR64w+hWjkxKjETdscw==", "cpu": [ "x64" ], @@ -13305,9 +13307,9 @@ } }, "node_modules/@oxc-transform-react/binding-freebsd-x64": { - "version": "0.148.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-freebsd-x64/-/binding-freebsd-x64-0.148.0.tgz", - "integrity": "sha512-5xxrqOGqO+gX6a+kyRtgIQsTByDcQayclyBKlgjyEiz2CXvvQ5SZn9/kGw6rbL7E+gwQQFzpVYLdYUM7zJlcXA==", + "version": "0.155.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-freebsd-x64/-/binding-freebsd-x64-0.155.0.tgz", + "integrity": "sha512-zZH3IX7fFrQA+zmy/amDrAe1OxqJl3vBmh+yROvsfPPaUWSn8+0Z451F9tMGhQ1iL6miBcDUHChBOVjfaJXBUg==", "cpu": [ "x64" ], @@ -13322,9 +13324,9 @@ } }, "node_modules/@oxc-transform-react/binding-linux-arm-gnueabihf": { - "version": "0.148.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.148.0.tgz", - "integrity": "sha512-2wDoBJ0aOo3ygoTu6TAV6UCIzlCTO8eEMWYVpLbX0iq/29WA6M3tjrRItWrE0suLP0uXcBDmWrsUu4NeqsM5og==", + "version": "0.155.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.155.0.tgz", + "integrity": "sha512-/WnW+j/lVYwNHzaFK6uONgbC9M1S7nTWV+ONe7F9pDz3xCfvhTeWTDrqN84/vrPbxT7Xh9j3PXVnU2cw+L2NHQ==", "cpu": [ "arm" ], @@ -13339,9 +13341,9 @@ } }, "node_modules/@oxc-transform-react/binding-linux-arm-musleabihf": { - "version": "0.148.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.148.0.tgz", - "integrity": "sha512-Lh73KZTuC2rh4Pff1XpGh4WkQCkWdtM04adrvDGZbxGTYWXaF6uWih5WdO5Pr+ZbkceBbHgKgEQL8Lp3M1TbxQ==", + "version": "0.155.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.155.0.tgz", + "integrity": "sha512-VzH6NX11/y0S4nhvYRYWP54G7VPhTy7MoHiAp1nOsRIOaGssUATn1/fLglgz2EgJmxr5AHy1SK9dDd/PUmW5iQ==", "cpu": [ "arm" ], @@ -13356,13 +13358,16 @@ } }, "node_modules/@oxc-transform-react/binding-linux-arm64-gnu": { - "version": "0.148.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.148.0.tgz", - "integrity": "sha512-HglSrS/bA2pKL20xCPgVlHvhhVycUC7QsSmBWJH5NqWVLPA2RdcvGyEtCWDKKTH0jbYJoHGrnz/aTq3KabwrnQ==", + "version": "0.155.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.155.0.tgz", + "integrity": "sha512-mLF5v1yiJghvDG5wUXWv9P0iEItM3EuFgSFfqdyY134fUsU5f7t5B7nxLr4T7DrEUWhs1+SwKNWZA+Mv4HSFiw==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -13373,13 +13378,16 @@ } }, "node_modules/@oxc-transform-react/binding-linux-arm64-musl": { - "version": "0.148.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.148.0.tgz", - "integrity": "sha512-kM0JCd+Mym4QIILv9a2CxqnriNozIz8KQjlw3kRrCdbEVEc2fIop4tvlcwYTRa+vKCWecZx627DsHTL7Ad9pzA==", + "version": "0.155.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.155.0.tgz", + "integrity": "sha512-EkBHEZKR4kDTU7+/mdleIu6VREm0zsQwDzQXAsMvbaRsmy8RkD8tOiXPNh6vyyqtahkLhEdj4vBs9c9h9iO66A==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -13390,13 +13398,16 @@ } }, "node_modules/@oxc-transform-react/binding-linux-ppc64-gnu": { - "version": "0.148.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.148.0.tgz", - "integrity": "sha512-7C8iUK/nw1wos9IJvW/RxROOz4TIj6g22a1B8ydPA5rBz8qMFNJNOPy3/UwhAd01F1h28LZlSeu6gewthGSL2A==", + "version": "0.155.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.155.0.tgz", + "integrity": "sha512-Iz26Sm7q5tO2h+da6leks3y62raTyZp32n3FX6WydxsecbFNzKpm+rKNPoti9Syd3u03rmkYlS8q7tPQMLLjUw==", "cpu": [ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -13407,13 +13418,16 @@ } }, "node_modules/@oxc-transform-react/binding-linux-riscv64-gnu": { - "version": "0.148.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.148.0.tgz", - "integrity": "sha512-0ZN6WTRcLxhPns7MYYKy7MQGTGf0jpEDACmdXEUquhPOizQ+7AsOcTnYR9Uss1dgG9G+2XAAd759i7jQXh0+2A==", + "version": "0.155.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.155.0.tgz", + "integrity": "sha512-CvKMO4MNKLdmy8Q75r1rwBz+VBcSYDObCyQaJOLWAwJnsWXiV7I788N4p2iWUYPWPmfvUd6d0UrrRz5ma5CCnw==", "cpu": [ "riscv64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -13424,13 +13438,16 @@ } }, "node_modules/@oxc-transform-react/binding-linux-riscv64-musl": { - "version": "0.148.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.148.0.tgz", - "integrity": "sha512-dcd2RAANPoKmHIKJlFCnvRlYJ7s268fswMgO7kmY6m5g35XMRlv71jZ/easkMW/RPr9vUPZyUZPHq0pTyMgXUg==", + "version": "0.155.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.155.0.tgz", + "integrity": "sha512-OfYtP9jEMZwdnjPLVrrMvy3CQSqwdQJw6Q6YU23fObDECt1lEe2o+TJ6nFgDVAKhTl7pc9dca3vGXRgNfiBfoA==", "cpu": [ "riscv64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -13441,13 +13458,16 @@ } }, "node_modules/@oxc-transform-react/binding-linux-s390x-gnu": { - "version": "0.148.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.148.0.tgz", - "integrity": "sha512-JdyMRM7bjODyVsDkyyJQfRCeM4MXmOzEu25RuPdz9dvPPKNb4gT9BrcnrDQiceQ2MLeLo9z7jawuCs4kjCALzw==", + "version": "0.155.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.155.0.tgz", + "integrity": "sha512-u5USLM5psVaNX2KEzTwfcKlzgGI16EY7Iod/Z/7Fw9FwBQiN+DUyhC99NL63aJ/Qyq0N5HIG4Y95DIEekc+EfQ==", "cpu": [ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -13458,13 +13478,16 @@ } }, "node_modules/@oxc-transform-react/binding-linux-x64-gnu": { - "version": "0.148.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.148.0.tgz", - "integrity": "sha512-UlpQs3EHU9p6oVJe/UifVer7L3OnNJULFaAmPBVz8p9wsFBHA2cBzOuD4cBTo5i+QmQMbSsrJ8qsGtPg+OCO2Q==", + "version": "0.155.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.155.0.tgz", + "integrity": "sha512-Kbddg5Eu/B+l6xW0CS5kaNNRK79IWu7o4NsDkn8SMz7k0N8CHV5watP0pscPIUuxcknBZkQxGqQ+b+qV7B18vA==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -13475,13 +13498,16 @@ } }, "node_modules/@oxc-transform-react/binding-linux-x64-musl": { - "version": "0.148.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-x64-musl/-/binding-linux-x64-musl-0.148.0.tgz", - "integrity": "sha512-+ZTnNcXOeUazfT1KNAmkmYayS82c/Z06AuZUaITbZKn+Hbp99SAI5HVE+HZXumwKGF89r0JOSQviIXfuyOLpSw==", + "version": "0.155.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-x64-musl/-/binding-linux-x64-musl-0.155.0.tgz", + "integrity": "sha512-hFZ69Mh6rZ1XeqSv4njgAB5WgKGBSTgH596EHusayzH+jtgczsMPK81k4SWlguKxrrzIqezLC8pyzhgruaNQGw==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -13492,9 +13518,9 @@ } }, "node_modules/@oxc-transform-react/binding-openharmony-arm64": { - "version": "0.148.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-openharmony-arm64/-/binding-openharmony-arm64-0.148.0.tgz", - "integrity": "sha512-QsbwZEBvJhAvdgUDekEi5B5m0H7aZE5f32M0OLmpUd2o94mA1gm2vIHP0YgaJ96SMHlBJwvCA+lvLzEiq7nyjQ==", + "version": "0.155.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-openharmony-arm64/-/binding-openharmony-arm64-0.155.0.tgz", + "integrity": "sha512-XikOLTZAchqLWNep6ptHZnA47EGlV0s7zpCA7iPRuiM/V9xfgqBbolryFu9/lNjoN6twmt8k0aRuW0frxSTVNQ==", "cpu": [ "arm64" ], @@ -13509,9 +13535,9 @@ } }, "node_modules/@oxc-transform-react/binding-win32-arm64-msvc": { - "version": "0.148.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.148.0.tgz", - "integrity": "sha512-OxXPOxtEn05uPpVjozZDaRq9jLWNLjldTZtYwuphGEx7F+c0s49Sj+NfB3TXBa2siDIcWHNnXK4crg1RiG5W7Q==", + "version": "0.155.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.155.0.tgz", + "integrity": "sha512-3aJKnNR9dBcSTfrrB3PxaYP5N1Myua8DZSHhe4SEp8M/SusvxdaK6bf9h6h801UsQB66S75fk4iTyEShLHZztA==", "cpu": [ "arm64" ], @@ -13526,9 +13552,9 @@ } }, "node_modules/@oxc-transform-react/binding-win32-ia32-msvc": { - "version": "0.148.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.148.0.tgz", - "integrity": "sha512-I7hP5FSEuUD+8Qle97YqC9BtvS69ERDu5afbGVpL0Wy9WtO5LtUnwwVj/u+75gEFMBYyh/eCyuah1J8eCYjWWg==", + "version": "0.155.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.155.0.tgz", + "integrity": "sha512-i2ZSZ79dSgVvC+Ksxju3/4p9+6vXzbeY7iOoBreL4mNOCjQziYChdv5Z921V9AZ4E4LVnD/NoHpez6M8T0Ga3A==", "cpu": [ "ia32" ], @@ -13543,9 +13569,9 @@ } }, "node_modules/@oxc-transform-react/binding-win32-x64-msvc": { - "version": "0.148.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.148.0.tgz", - "integrity": "sha512-nUv3W/fIaMuyQN479OZcJAA4uuuL/YzdQI490OgrBrzP69vXyMYnIVivptDNJF/gu37AmT2vW2+8ZWx33ghjdQ==", + "version": "0.155.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.155.0.tgz", + "integrity": "sha512-lrPIzcUHw/hB/LuT/G639+IrPCvvMlthF3xCE9grz1IbsSk+22sb5zyCxTvmpBxZR7/E1ckwZdnw7AmpAd+7Jg==", "cpu": [ "x64" ], @@ -13882,6 +13908,437 @@ "node": "^20.19.0 || >=22.12.0" } }, + "node_modules/@oxlint-tsgolint/darwin-arm64": { + "version": "7.0.2003", + "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/darwin-arm64/-/darwin-arm64-7.0.2003.tgz", + "integrity": "sha512-TgV33rXr6ueXBwvc+0nssUkTBSXHJxv77I8p4RCjDjCnvexHtmoPiudVRDfj3S3puMDdeQdHW6jdUgUPy3nr/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxlint-tsgolint/darwin-x64": { + "version": "7.0.2003", + "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/darwin-x64/-/darwin-x64-7.0.2003.tgz", + "integrity": "sha512-hY3FMAjIaPDdK3FNxyRXRfHPOFeoBn6dmnLyKZgQ2IbTTD1adXhl6PfCIqHhCPvurigv7nf7mYuWZZ19MmJzmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxlint-tsgolint/linux-arm64": { + "version": "7.0.2003", + "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/linux-arm64/-/linux-arm64-7.0.2003.tgz", + "integrity": "sha512-eAET4JpyfBbg8SO0K74o4R55tEjVC292pIyxGOw2XGf8x/HrPNyxwQ478jMYOM54FIVOHc8sJ6VRHxluy6lucw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxlint-tsgolint/linux-x64": { + "version": "7.0.2003", + "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/linux-x64/-/linux-x64-7.0.2003.tgz", + "integrity": "sha512-GXdyO/XyqDJ3s/llR/oOktLsYNjZtWSQBy0JMc+/0gsNPvTcseKPhn9c6KcFyWmnr6H4vljYALbujonqSzzEGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxlint-tsgolint/win32-arm64": { + "version": "7.0.2003", + "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/win32-arm64/-/win32-arm64-7.0.2003.tgz", + "integrity": "sha512-TWauXnPfet0VgpmrstoAK58eJ4gbuwPUuUTQmYQAzE/RG1CpnxXtrKUJULf6lyerPE4KN3gM+DflIA1hOH+38Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oxlint-tsgolint/win32-x64": { + "version": "7.0.2003", + "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/win32-x64/-/win32-x64-7.0.2003.tgz", + "integrity": "sha512-DoRmfe7j8VqNlukp8liRVW45GQDhzRccNenjD/pdzelgtffW47pCMd1xbJLkPaPbKnTwID3onn3VZlL7JbebEA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oxlint/binding-android-arm-eabi": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.89.0.tgz", + "integrity": "sha512-Pflpba5F3gRAQ12sWglRxvWTHEdAusNDvmi4Y5N1T5SY1AGkM7AixOnET8qkrnLJ/F32qAlNokO6eCknT8tmnw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-android-arm64": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.89.0.tgz", + "integrity": "sha512-dYmyD1x7LaPlb79vZWUcpTShk8dI9z1I+IubtqrBxWNJaDhQdPHwcsvg7q5StTU4xWyd9AwPfLaTYFZY8NZgtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-arm64": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.89.0.tgz", + "integrity": "sha512-Z/oxOPiIqiS7fItRdxaaOEC+6GD3OpGi0IxoR0dMefzQHolsKLzScw2DNcu/5gUPok3tv+G9mslvYB5bo3i0qg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-x64": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.89.0.tgz", + "integrity": "sha512-eAjTkHSjAxrdCPJxO6y5sPQL5pYLKb2kvxZz9UU7tXnOZk9I/HLtkvx9S7GHY883k+AmL/eC7mqFt+w7WCwk0w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-freebsd-x64": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.89.0.tgz", + "integrity": "sha512-0lNCA9Tl1n1FKpqy2/9ckp0kIuSrtJ/l9bMRgaEhOtM7TsOHshFuwOcvW8oA9teXLMOJT61LjEKWiaHDFGxF/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-gnueabihf": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.89.0.tgz", + "integrity": "sha512-OTFEvi9W+JczaZD1Caj17SGCoW8H7TE+fvx0D7ATEWa1Lohn91UMxFQ6r145SiGy/yCxeIwptMys/RAM7gSVSQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-musleabihf": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.89.0.tgz", + "integrity": "sha512-fRxgNHvBLswj6MrhW2uBq5UDhPRQlCbHeq/Nf8yuG9zOxhDsXqX/mi2PX1axgsQxMm88el7HLiJAzglAnflYxw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-gnu": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.89.0.tgz", + "integrity": "sha512-v9ENzHvwrQoYSWTtviH1pOYc7R9UovDxVofnck50cNmPihRRTcrg5X0UlRbLOZ7IVtxPOlh2hb7kDO+FpE8SfQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-musl": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.89.0.tgz", + "integrity": "sha512-vfffe8mQ484AzVadlUOFpgL+JDwhvs9hrzxTgQP1FVfi4ZEUUYeffXlzC9dFV7Dgnu5s/KrNKvfmG6HxBd/ISQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-ppc64-gnu": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.89.0.tgz", + "integrity": "sha512-Buonlp4hVS7RGNP5M7bPpGNQrenyzA2C64JcO/oz9C1c6dGOXJ7VEAnaiAMY7voou0JYyCJdvDVlFQc5DZX3DQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-gnu": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.89.0.tgz", + "integrity": "sha512-1eVwJJrJ3lYIPT+cm7BcBBZZXY6IQZP/oyonaruTor/MrVyWOpvnwt1oD5CLdsHg5aVBIxXodQSvt+1gK6ck5w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-musl": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.89.0.tgz", + "integrity": "sha512-fRQQsKUGShdEL5Fl9o9uhj3grkZbeXKLv0icQ5tspUCNYSDy8wzzEcP1WC2bKdTQ0rSszbKlO3iiHORxV22DLg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-s390x-gnu": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.89.0.tgz", + "integrity": "sha512-dEtYjIy6+fHcjKklsmtmMkZRhwHr2UbzjmpvuPtQYSeN9awQ2hx5JLms5Wvri+8P/NZtD8v5hfgHALUql+/Ycg==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-gnu": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.89.0.tgz", + "integrity": "sha512-hqjwIj5KOYM8Wjef/p7IruNi1iByLhSzbMBk62y22P/E8q7Jj0Pp+DeUTKTRYyZYCDtU1wEGlnWOnttHbyNIww==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-musl": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.89.0.tgz", + "integrity": "sha512-iAQN3e6o9UgUCcdGwqRx/jYcKbG3HK7ur3rH6lTqVcQeNGZ85AeWIg6cgn/edwFQ0ofEZZItMwkzyCVTahnvEA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-openharmony-arm64": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.89.0.tgz", + "integrity": "sha512-MNxjEByz3N/7Q4qFQuPWvc14TpN9LoD3pECEDsJTZbP/wyB7U+hduUXEl7QAbbgVezFwsb9sWtNUO6XVBndOhg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-arm64-msvc": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.89.0.tgz", + "integrity": "sha512-xxe+i9wfuFso/VnoL4mfj0UlzE+cfCFiUB06vkXCMyQdgVVmItF5FfAF4tATAktSU6uHruWR+FjqvAT9cHZZzg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-ia32-msvc": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.89.0.tgz", + "integrity": "sha512-saFy07cO94E664glwgLmKLI40HgTL1PY86y4T6+ud4niX4H0Kdqjcmqmjps7mQNdBytV5guGbXDdyQtRfeLbLA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-x64-msvc": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.89.0.tgz", + "integrity": "sha512-hLFJ/o0f/dTJh/EW0vmcbREYSQO9tYgfiAAlsOkiLFiJuRRWvYtFELUXN5o2ijpSo/tlO1n+YlWN58tZwPG18A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/@peggyjs/from-mem": { "version": "1.3.0", "dev": true, @@ -35371,9 +35828,9 @@ } }, "node_modules/oxc-transform-react": { - "version": "0.148.0", - "resolved": "https://registry.npmjs.org/oxc-transform-react/-/oxc-transform-react-0.148.0.tgz", - "integrity": "sha512-0EreEjngNalcw26tFo2UPiD3tZm8KqumYFZRDb5weWdz3ENLea7+GV0kzLaSCdaq3uF4rQ0jw1GAgW41HLn90g==", + "version": "0.155.0", + "resolved": "https://registry.npmjs.org/oxc-transform-react/-/oxc-transform-react-0.155.0.tgz", + "integrity": "sha512-JsZb0yduU1JdSj1RrNov2Ysio68LW9VKa10vkAJehy3NsE6UZrfcjmTLrStBRVF40YKxN30I+Cq8zJxIXzLqHQ==", "dev": true, "license": "MIT", "engines": { @@ -35383,25 +35840,25 @@ "url": "https://github.com/sponsors/oxc-project" }, "optionalDependencies": { - "@oxc-transform-react/binding-android-arm-eabi": "0.148.0", - "@oxc-transform-react/binding-android-arm64": "0.148.0", - "@oxc-transform-react/binding-darwin-arm64": "0.148.0", - "@oxc-transform-react/binding-darwin-x64": "0.148.0", - "@oxc-transform-react/binding-freebsd-x64": "0.148.0", - "@oxc-transform-react/binding-linux-arm-gnueabihf": "0.148.0", - "@oxc-transform-react/binding-linux-arm-musleabihf": "0.148.0", - "@oxc-transform-react/binding-linux-arm64-gnu": "0.148.0", - "@oxc-transform-react/binding-linux-arm64-musl": "0.148.0", - "@oxc-transform-react/binding-linux-ppc64-gnu": "0.148.0", - "@oxc-transform-react/binding-linux-riscv64-gnu": "0.148.0", - "@oxc-transform-react/binding-linux-riscv64-musl": "0.148.0", - "@oxc-transform-react/binding-linux-s390x-gnu": "0.148.0", - "@oxc-transform-react/binding-linux-x64-gnu": "0.148.0", - "@oxc-transform-react/binding-linux-x64-musl": "0.148.0", - "@oxc-transform-react/binding-openharmony-arm64": "0.148.0", - "@oxc-transform-react/binding-win32-arm64-msvc": "0.148.0", - "@oxc-transform-react/binding-win32-ia32-msvc": "0.148.0", - "@oxc-transform-react/binding-win32-x64-msvc": "0.148.0" + "@oxc-transform-react/binding-android-arm-eabi": "0.155.0", + "@oxc-transform-react/binding-android-arm64": "0.155.0", + "@oxc-transform-react/binding-darwin-arm64": "0.155.0", + "@oxc-transform-react/binding-darwin-x64": "0.155.0", + "@oxc-transform-react/binding-freebsd-x64": "0.155.0", + "@oxc-transform-react/binding-linux-arm-gnueabihf": "0.155.0", + "@oxc-transform-react/binding-linux-arm-musleabihf": "0.155.0", + "@oxc-transform-react/binding-linux-arm64-gnu": "0.155.0", + "@oxc-transform-react/binding-linux-arm64-musl": "0.155.0", + "@oxc-transform-react/binding-linux-ppc64-gnu": "0.155.0", + "@oxc-transform-react/binding-linux-riscv64-gnu": "0.155.0", + "@oxc-transform-react/binding-linux-riscv64-musl": "0.155.0", + "@oxc-transform-react/binding-linux-s390x-gnu": "0.155.0", + "@oxc-transform-react/binding-linux-x64-gnu": "0.155.0", + "@oxc-transform-react/binding-linux-x64-musl": "0.155.0", + "@oxc-transform-react/binding-openharmony-arm64": "0.155.0", + "@oxc-transform-react/binding-win32-arm64-msvc": "0.155.0", + "@oxc-transform-react/binding-win32-ia32-msvc": "0.155.0", + "@oxc-transform-react/binding-win32-x64-msvc": "0.155.0" } }, "node_modules/oxfmt": { @@ -35456,6 +35913,73 @@ } } }, + "node_modules/oxlint": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.89.0.tgz", + "integrity": "sha512-a25hkfo61okGPvJz432fRIPoxlKtNnOVy9w4rzrmGgNm4iVTPwpSQlmdqNWnp6p9QyyPVfAZRgnf5XvSBZFk/A==", + "dev": true, + "license": "MIT", + "bin": { + "oxlint": "bin/oxlint" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/oxc-project" + }, + "optionalDependencies": { + "@oxlint/binding-android-arm-eabi": "1.89.0", + "@oxlint/binding-android-arm64": "1.89.0", + "@oxlint/binding-darwin-arm64": "1.89.0", + "@oxlint/binding-darwin-x64": "1.89.0", + "@oxlint/binding-freebsd-x64": "1.89.0", + "@oxlint/binding-linux-arm-gnueabihf": "1.89.0", + "@oxlint/binding-linux-arm-musleabihf": "1.89.0", + "@oxlint/binding-linux-arm64-gnu": "1.89.0", + "@oxlint/binding-linux-arm64-musl": "1.89.0", + "@oxlint/binding-linux-ppc64-gnu": "1.89.0", + "@oxlint/binding-linux-riscv64-gnu": "1.89.0", + "@oxlint/binding-linux-riscv64-musl": "1.89.0", + "@oxlint/binding-linux-s390x-gnu": "1.89.0", + "@oxlint/binding-linux-x64-gnu": "1.89.0", + "@oxlint/binding-linux-x64-musl": "1.89.0", + "@oxlint/binding-openharmony-arm64": "1.89.0", + "@oxlint/binding-win32-arm64-msvc": "1.89.0", + "@oxlint/binding-win32-ia32-msvc": "1.89.0", + "@oxlint/binding-win32-x64-msvc": "1.89.0" + }, + "peerDependencies": { + "oxlint-tsgolint": ">=7.0.2003", + "vite-plus": "*" + }, + "peerDependenciesMeta": { + "oxlint-tsgolint": { + "optional": true + }, + "vite-plus": { + "optional": true + } + } + }, + "node_modules/oxlint-tsgolint": { + "version": "7.0.2003", + "resolved": "https://registry.npmjs.org/oxlint-tsgolint/-/oxlint-tsgolint-7.0.2003.tgz", + "integrity": "sha512-VnK4zlqgmgq/7ZcjzCk/WpN8kKFsYGcB85Io9qT3wL4K8Un3RKmJkp793crNETieMusPMKOA4a+Mw2wbteI6TQ==", + "dev": true, + "license": "MIT", + "bin": { + "tsgolint": "bin/tsgolint.js" + }, + "optionalDependencies": { + "@oxlint-tsgolint/darwin-arm64": "7.0.2003", + "@oxlint-tsgolint/darwin-x64": "7.0.2003", + "@oxlint-tsgolint/linux-arm64": "7.0.2003", + "@oxlint-tsgolint/linux-x64": "7.0.2003", + "@oxlint-tsgolint/win32-arm64": "7.0.2003", + "@oxlint-tsgolint/win32-x64": "7.0.2003" + } + }, "node_modules/p-limit": { "version": "3.1.0", "license": "MIT", diff --git a/package.json b/package.json index 2023e9cdc908..aa24ddbfb0b4 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "perf-local": "NODE_OPTIONS=--experimental-vm-modules npx reassure --testMatch '/tests/perf-local/**/*.perf-local.ts'", "typecheck": "bun scripts/typecheck.ts", "lint": "bun scripts/lint/index.ts", + "lint-oxlint": "oxlint .", "lint-changed": "./scripts/lintChanged.sh", "lint-watch": "onchange '**/*.{js,jsx,ts,tsx,mjs,cjs}' -- bun scripts/lint/index.ts {{changed}}", "eslint-report": "bun scripts/eslint-report.ts", @@ -349,8 +350,10 @@ "nitrogen": "0.37.1", "onchange": "^7.1.0", "openai": "^7.0.0", - "oxc-transform-react": "0.148.0", + "oxc-transform-react": "0.155.0", "oxfmt": "^0.55.0", + "oxlint": "1.89.0", + "oxlint-tsgolint": "7.0.2003", "patch-package": "^8.1.0-canary.1", "peggy": "^4.0.3", "portfinder": "^1.0.34", @@ -373,6 +376,7 @@ }, "overrides": { "typescript": "$typescript", + "vite-plus": "^0.1.15", "@typescript-eslint/eslint-plugin": "8.58.1", "@typescript-eslint/parser": "8.58.1", "@typescript-eslint/typescript-estree": "8.61.0", diff --git a/scripts/lint/formatters/JSONFormatter.ts b/scripts/lint/formatters/JSONFormatter.ts new file mode 100644 index 000000000000..c00dff3f84d9 --- /dev/null +++ b/scripts/lint/formatters/JSONFormatter.ts @@ -0,0 +1,26 @@ +import type {FormatterResult, LintMessage} from '../types'; + +import Formatter from '../Formatter'; +import {LINT_SEVERITY} from '../types'; + +type JSONReport = { + messages: LintMessage[]; + errorCount: number; + warningCount: number; +}; + +class JSONFormatter extends Formatter { + readonly name = 'json'; + + format(messages: LintMessage[]): FormatterResult { + const report: JSONReport = { + messages, + errorCount: messages.filter((message) => message.severity === LINT_SEVERITY.ERROR).length, + warningCount: messages.filter((message) => message.severity === LINT_SEVERITY.WARNING).length, + }; + return {text: JSON.stringify(report), errorCount: report.errorCount, warningCount: report.warningCount}; + } +} + +export default JSONFormatter; +export type {JSONReport}; diff --git a/scripts/lint/index.ts b/scripts/lint/index.ts index ca7fe1fb25a0..f8df570bd40a 100644 --- a/scripts/lint/index.ts +++ b/scripts/lint/index.ts @@ -9,25 +9,59 @@ * bun scripts/lint/index.ts --timings -> print per-stage wall times */ +import type {TupleToUnion} from 'type-fest'; + import CLI from 'expensify-common/CLI'; +import type Linter from './Linter'; + import checkOnyxConnectBypass from '../checkOnyxConnectBypass'; import Bench from '../utils/Bench'; import ESLintLinter from './eslint/ESLintLinter'; +import JSONFormatter from './formatters/JSONFormatter'; import StylishFormatter from './formatters/StylishFormatter'; import Pipeline from './LintPipeline'; +import OxlintLinter from './oxlint/OxlintLinter'; import ReactCompilerFilter from './processors/ReactCompilerFilter'; -import Seatbelt, {resolveSeatbeltOptions} from './processors/Seatbelt'; +import Seatbelt, {SEATBELT_TSV_BY_LINTER, resolveSeatbeltOptions} from './processors/Seatbelt'; import StratifyNoDeprecated from './processors/StratifyNoDeprecated'; const projectRoot = `${import.meta.dir}/../..`; +const LINTER_NAMES = ['eslint', 'oxlint'] as const; +type LinterName = TupleToUnion; + +function isLinterName(value: string): value is LinterName { + return LINTER_NAMES.some((name) => name === value); +} + +function parseLinterName(value: string): LinterName { + if (!isLinterName(value)) { + throw new Error(`Unknown linter "${value}". Expected one of: ${LINTER_NAMES.join(', ')}`); + } + return value; +} + +const FORMAT_NAMES = ['stylish', 'json'] as const; +type FormatName = TupleToUnion; + +function isFormatName(value: string): value is FormatName { + return FORMAT_NAMES.some((name) => name === value); +} + +function parseFormatName(value: string): FormatName { + if (!isFormatName(value)) { + throw new Error(`Unknown format "${value}". Expected one of: ${FORMAT_NAMES.join(', ')}`); + } + return value; +} + /* CLI argv uses kebab-case for flags documented in help */ /* eslint-disable @typescript-eslint/naming-convention */ const cli = new CLI({ flags: { 'no-cache': { - description: 'Disable the ESLint content cache', + description: 'Disable the ESLint content cache (no-op under Oxlint, which has none)', }, 'show-warnings': { description: 'Include grandfathered seatbelt warnings in the report', @@ -39,6 +73,23 @@ const cli = new CLI({ description: 'Print per-stage wall times', }, }, + namedArgs: { + linter: { + description: `Which linter produces the diagnostics (${LINTER_NAMES.join(' | ')})`, + default: 'eslint', + parse: parseLinterName, + }, + format: { + description: `How to print the report (${FORMAT_NAMES.join(' | ')})`, + default: 'stylish', + parse: parseFormatName, + }, + shards: { + description: 'Oxlint only: processes to fan the JS plugins across (0 = auto by cores and free memory; 1 = single process)', + default: 0, + parse: (value: string) => Number.parseInt(value, 10), + }, + }, positionalArgs: [ { name: 'targets', @@ -54,15 +105,22 @@ const lintTargets = cli.positionalArgs.targets.length > 0 ? cli.positionalArgs.t const showTimings = cli.flags.timings || process.env.LINT_TIMINGS === '1'; const bench = new Bench(); -const pipeline = new Pipeline( - projectRoot, - new ESLintLinter({ +function makeLinter(name: LinterName): Linter { + if (name === 'oxlint') { + return new OxlintLinter({projectRoot, fix: cli.flags.fix, shards: cli.namedArgs.shards > 0 ? String(cli.namedArgs.shards) : undefined}); + } + return new ESLintLinter({ projectRoot, useCache: !cli.flags['no-cache'], fix: cli.flags.fix, - }), - [new ReactCompilerFilter(), new StratifyNoDeprecated(), new Seatbelt(resolveSeatbeltOptions(projectRoot))], - new StylishFormatter(projectRoot, cli.flags['show-warnings']), + }); +} + +const pipeline = new Pipeline( + projectRoot, + makeLinter(cli.namedArgs.linter), + [new ReactCompilerFilter(), new StratifyNoDeprecated(), new Seatbelt(resolveSeatbeltOptions(projectRoot, process.env, SEATBELT_TSV_BY_LINTER[cli.namedArgs.linter]))], + cli.namedArgs.format === 'json' ? new JSONFormatter() : new StylishFormatter(projectRoot, cli.flags['show-warnings']), bench, ); diff --git a/scripts/lint/oxlint/OxlintLinter.ts b/scripts/lint/oxlint/OxlintLinter.ts new file mode 100644 index 000000000000..5af5c65ac3a4 --- /dev/null +++ b/scripts/lint/oxlint/OxlintLinter.ts @@ -0,0 +1,498 @@ +import {$} from 'bun'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; + +import type {LintFileResult, LintMessage, LintSeverity, LinterResult} from '../types'; + +// eslint-disable-next-line import/extensions +import {oxlintCodeToESLintRuleID} from '../../../config/oxlint/ruleNames.mjs'; +import Linter from '../Linter'; +import {LINT_SEVERITY} from '../types'; + +const OXLINT_FILE_COUNT_KEY = 'number_of_files' as const; +const OXLINT_CONFIG_FILE = 'oxlint.config.mts'; +// Not `npx`: it passes the command to `sh -c` as one string, and Linux caps a single argv element at +// 128 KB, so a shard's file list fails to exec with E2BIG (exit 249). macOS has no per-element cap. +const OXLINT_BIN = path.join('node_modules', '.bin', 'oxlint'); + +type OxlintSpan = { + offset: number; + length: number; + line?: number; + column?: number; +}; + +type OxlintLabel = { + span?: OxlintSpan; +}; + +type OxlintDiagnostic = { + message: string; + code?: string | null; + severity?: string; + filename: string; + help?: string; + note?: string; + labels?: OxlintLabel[]; +}; + +type OxlintJSONReport = { + diagnostics: OxlintDiagnostic[]; + [OXLINT_FILE_COUNT_KEY]?: number; +}; + +type OxlintJSPlugin = string | {name: string; specifier: string}; + +type OxlintRules = Record; + +type OxlintOverride = { + files?: string[]; + jsPlugins?: OxlintJSPlugin[]; + rules?: OxlintRules; + [key: string]: unknown; +}; + +type OxlintConfig = { + jsPlugins?: OxlintJSPlugin[]; + options?: Record; + rules?: OxlintRules; + overrides?: OxlintOverride[]; + [key: string]: unknown; +}; + +type OxlintLinterOptions = { + projectRoot: string; + fix: boolean; + threads?: string; + shards?: string; +}; + +type OxlintLeg = { + label: string; + args: string[]; + lintedFiles: readonly string[]; +}; + +const FATAL_EXIT_CODE = 2; +const SIGNAL_EXIT_CODE = 128; +const OXLINT_WARNING_SEVERITIES = new Set(['warning', 'advice']); +const GIB = 1073741824; + +// Workaround for oxlint running every JS plugin on one thread (oxc#26621): JS-plugin rules are fanned +// across `--threads=1` processes with type information off, and type-aware rules run once in a separate +// process, because each oxlint process spawns its own multi-GB `oxlint-tsgolint` type program. +const JS_SHARD_MEM_GB = 2; +const TYPE_AWARE_MEM_GB = 10; + +// On macOS `os.freemem()` counts only free pages and ignores the inactive, speculative and purgeable +// pages the kernel reclaims on demand, so it reads a few GB on an idle machine with tens of GB available. +function availableMemoryBytes(): number { + if (process.platform === 'darwin') { + const vmStat = Bun.spawnSync(['vm_stat']); + if (vmStat.success) { + const text = vmStat.stdout.toString(); + const pageSize = Number(/page size of (\d+)/.exec(text)?.[1]); + const pages = (name: string) => Number(new RegExp(`^Pages ${name}:\\s+(\\d+)`, 'm').exec(text)?.[1] ?? 0); + if (pageSize > 0) { + return (pages('free') + pages('inactive') + pages('speculative') + pages('purgeable')) * pageSize; + } + } + } + return os.freemem(); +} + +function defaultShardCount(cpus = os.availableParallelism(), availableBytes = availableMemoryBytes()): number { + const byCpu = Math.floor(cpus / 2); + const byMem = Math.floor((availableBytes / GIB - TYPE_AWARE_MEM_GB) / JS_SHARD_MEM_GB); + return Math.max(1, Math.min(byCpu, byMem)); +} + +function resolveShardCount(override: string | undefined): number { + if (override === undefined || override.trim() === '') { + return defaultShardCount(); + } + const parsed = Number.parseInt(override, 10); + return Number.isFinite(parsed) && parsed >= 1 ? parsed : 1; +} + +function shardFiles(files: readonly string[], count: number): string[][] { + if (count <= 1) { + return [files.slice()]; + } + const shards: string[][] = Array.from({length: Math.min(count, files.length)}, () => []); + for (const [index, file] of files.entries()) { + shards.at(index % shards.length)?.push(file); + } + return shards; +} + +// Mirrors how oxlint names a package plugin: `eslint-plugin-lodash` -> `lodash`, `@scope/eslint-plugin-x` -> `@scope/x`. +function jsPluginName(plugin: OxlintJSPlugin): string { + if (typeof plugin !== 'string') { + return plugin.name; + } + return plugin.replace(/(^|\/)eslint-plugin(-|$)/, '$1').replace(/\/$/, ''); +} + +function withoutRulesFrom(rules: OxlintRules | undefined, pluginNames: ReadonlySet): OxlintRules { + return Object.fromEntries(Object.entries(rules ?? {}).filter(([rule]) => ![...pluginNames].some((name) => rule.startsWith(`${name}/`)))); +} + +function deriveLegConfigs(config: OxlintConfig): { + jsPlugins: OxlintConfig; + typeAware: OxlintConfig; +} { + const pluginNames = new Set([...(config.jsPlugins ?? []), ...(config.overrides ?? []).flatMap((override) => override.jsPlugins ?? [])].map(jsPluginName)); + const typeAware: OxlintConfig = { + ...config, + rules: withoutRulesFrom(config.rules, pluginNames), + overrides: (config.overrides ?? []).map((override) => { + const stripped: OxlintOverride = { + ...override, + rules: withoutRulesFrom(override.rules, pluginNames), + }; + delete stripped.jsPlugins; + return stripped; + }), + }; + delete typeAware.jsPlugins; + return { + jsPlugins: {...config, options: {...config.options, typeAware: false}}, + typeAware, + }; +} + +function messageKey(message: LintMessage): string { + return [message.ruleID, message.severity, message.line, message.column, message.message].join('\u0000'); +} + +function mergeShardResults(results: LinterResult[]): LinterResult { + const fatalShard = results.find((result) => result.exitCode >= FATAL_EXIT_CODE); + if (fatalShard) { + return fatalShard; + } + const byFile = new Map; messages: LintMessage[]}>(); + for (const result of results) { + for (const file of result.files) { + const entry = byFile.get(file.filePath) ?? { + seen: new Set(), + messages: [], + }; + entry.messages.push(...file.messages.filter((message) => !entry.seen.has(messageKey(message)))); + for (const message of file.messages) { + entry.seen.add(messageKey(message)); + } + byFile.set(file.filePath, entry); + } + } + return { + files: [...byFile].map(([filePath, {messages}]) => ({ + filePath, + messages, + })), + exitCode: results.reduce((worst, result) => Math.max(worst, result.exitCode), 0), + stderr: results + .map((result) => result.stderr) + .filter(Boolean) + .join('\n'), + }; +} + +function normalizeSeverity(severity: string | undefined): LintSeverity { + return OXLINT_WARNING_SEVERITIES.has(severity ?? '') ? LINT_SEVERITY.WARNING : LINT_SEVERITY.ERROR; +} + +/** Oxlint splits a finding across `message`, `help` and `note`; ESLint has only `message`. */ +function joinDiagnosticText(diagnostic: OxlintDiagnostic): string { + return [diagnostic.message, diagnostic.help, diagnostic.note].filter(Boolean).join('\n'); +} + +/** Oxlint reports cwd-relative paths. They must be absolute or every seatbelt row silently misses. */ +function toAbsolute(projectRoot: string, filename: string): string { + return path.isAbsolute(filename) ? filename : path.resolve(projectRoot, filename); +} + +function toLintMessage(projectRoot: string, diagnostic: OxlintDiagnostic, ruleID: string): LintMessage { + const span = diagnostic.labels?.at(0)?.span; + return { + filePath: toAbsolute(projectRoot, diagnostic.filename), + ruleID, + severity: normalizeSeverity(diagnostic.severity), + message: joinDiagnosticText(diagnostic), + line: span?.line ?? 0, + column: span?.column ?? 0, + }; +} + +/** + * `lintedFiles` comes from a separate `--debug=files` pass because oxlint's JSON omits every file + * that produced no diagnostic. + */ +function normalizeOxlintDiagnostics(diagnostics: OxlintDiagnostic[], projectRoot: string, lintedFiles: readonly string[]): LintFileResult[] { + const byFile = new Map(); + for (const filePath of lintedFiles) { + byFile.set(toAbsolute(projectRoot, filePath), []); + } + + for (const diagnostic of diagnostics) { + const filePath = toAbsolute(projectRoot, diagnostic.filename); + const messages = byFile.get(filePath) ?? []; + messages.push(toLintMessage(projectRoot, diagnostic, oxlintCodeToESLintRuleID(diagnostic.code ?? ''))); + byFile.set(filePath, messages); + } + + return [...byFile].map(([filePath, messages]) => ({filePath, messages})); +} + +/** Oxlint prints warnings such as "No files found to lint." to stdout, ahead of the JSON. */ +function extractJSONObject(text: string): string | null { + const start = text.indexOf('{'); + const end = text.lastIndexOf('}'); + if (start < 0 || end <= start) { + return null; + } + return text.slice(start, end + 1); +} + +// A killed oxlint writes nothing; a killed `oxlint-tsgolint` makes oxlint print +// `Error running tsgolint: "exit status: exit status: 1"` instead of a report and exit 1. +function producedNoJSON(stdout: string): boolean { + return extractJSONObject(stdout) === null; +} + +function isTransientFailure(stdout: string, exitCode: number): boolean { + return producedNoJSON(stdout) || exitCode >= SIGNAL_EXIT_CODE; +} + +function fatal(reason: string, stdout: string, stderr: string, exitCode: number): LinterResult { + return { + files: [], + exitCode: Math.max(FATAL_EXIT_CODE, exitCode), + stderr: `${stderr}\n${reason}\n${stdout.slice(0, 500)}`.trim(), + }; +} + +function isOxlintConfig(value: unknown): value is OxlintConfig { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +/** The config is a TypeScript module, so it is imported rather than parsed; bun runs `.mts` natively. */ +async function loadOxlintConfig(projectRoot: string): Promise { + const loaded: unknown = await import(path.join(projectRoot, OXLINT_CONFIG_FILE)); + const config = typeof loaded === 'object' && loaded !== null && 'default' in loaded ? loaded.default : undefined; + if (!isOxlintConfig(config)) { + throw new Error(`${OXLINT_CONFIG_FILE} does not default-export a config object`); + } + return config; +} + +function isOxlintReport(value: unknown): value is OxlintJSONReport { + return typeof value === 'object' && value !== null && 'diagnostics' in value && Array.isArray(value.diagnostics); +} + +/** + * Oxlint exits 1 for findings, for no files matched, and for a config that failed to parse. Only + * the first is a normal run, and `LintPipeline` treats everything at or below 1 as one, so the + * other two are promoted here. A config error is recognizable by producing no JSON at all. + */ +function parseOxlintStdout(stdout: string, stderr: string, exitCode: number, projectRoot: string, lintedFiles: readonly string[]): LinterResult { + const jsonText = extractJSONObject(stdout); + if (!jsonText) { + return fatal('Failed to parse Oxlint JSON output.', stdout, stderr, exitCode); + } + + let parsed: unknown; + try { + parsed = JSON.parse(jsonText); + } catch { + return fatal('Failed to parse Oxlint JSON output.', stdout, stderr, exitCode); + } + if (!isOxlintReport(parsed)) { + return fatal('Oxlint JSON output has no `diagnostics` array.', stdout, stderr, exitCode); + } + + // A diagnostic with no `code` is a crashing JS plugin, not a finding. + const codeless = parsed.diagnostics.filter((diagnostic) => !diagnostic.code); + if (codeless.length > 0) { + const sample = codeless + .slice(0, 5) + .map((diagnostic) => ` ${diagnostic.filename}: ${diagnostic.message}`) + .join('\n'); + return fatal(`Oxlint reported ${codeless.length} diagnostic(s) with no rule code, which means a JS plugin threw:\n${sample}`, '', stderr, exitCode); + } + + return { + files: normalizeOxlintDiagnostics(parsed.diagnostics, projectRoot, lintedFiles), + exitCode, + stderr, + }; +} + +/** + * `--type-aware` is not passed: `oxlint.config.mts` already sets `options.typeAware`. + * `--no-error-on-unmatched-pattern` is not passed either, because it turns a mistyped path into a + * silent clean pass; the empty-file-list case is detected explicitly instead. + * Oxlint has no `--cache`, so the pipeline's `--no-cache` flag has no effect here. + */ +class OxlintLinter extends Linter { + readonly name = 'oxlint'; + + constructor(private readonly options: OxlintLinterOptions) { + super(); + } + + private async listLintedFiles(targets: string[]): Promise { + const result = await $`${OXLINT_BIN} --debug=files ${targets}`.cwd(this.options.projectRoot).nothrow().quiet(); + return result.stdout + .toString() + .split('\n') + .map((line) => line.trim()) + .filter(Boolean); + } + + private async listLintedFilesWithRetry(targets: string[]): Promise<{files: string[]; retried: boolean}> { + const files = await this.listLintedFiles(targets); + if (files.length > 0) { + return {files, retried: false}; + } + return {files: await this.listLintedFiles(targets), retried: true}; + } + + private runOxlint(args: string[]) { + return $`${OXLINT_BIN} ${args}` + .cwd(this.options.projectRoot) + .env({...process.env, LINT_PIPELINE: '1'}) + .nothrow() + .quiet(); + } + + private async writeLegConfigs(): Promise<{ + jsPluginsConfig: string; + typeAwareConfig: string; + remove: () => void; + }> { + const {jsPlugins, typeAware} = deriveLegConfigs(await loadOxlintConfig(this.options.projectRoot)); + const jsPluginsConfig = `.oxlintrc.js-plugins.${process.pid}.json`; + const typeAwareConfig = `.oxlintrc.type-aware.${process.pid}.json`; + fs.writeFileSync(path.join(this.options.projectRoot, jsPluginsConfig), JSON.stringify(jsPlugins)); + fs.writeFileSync(path.join(this.options.projectRoot, typeAwareConfig), JSON.stringify(typeAware)); + return { + jsPluginsConfig, + typeAwareConfig, + remove: () => { + fs.rmSync(path.join(this.options.projectRoot, jsPluginsConfig), { + force: true, + }); + fs.rmSync(path.join(this.options.projectRoot, typeAwareConfig), { + force: true, + }); + }, + }; + } + + private async runLegs(legs: OxlintLeg[], notes: string[]): Promise { + const outputs = []; + if (this.options.fix) { + for (const leg of legs) { + outputs.push(await this.runOxlint(leg.args)); + } + } else { + outputs.push(...(await Promise.all(legs.map((leg) => this.runOxlint(leg.args))))); + } + + const parsed: LinterResult[] = []; + for (const [index, leg] of legs.entries()) { + let output = outputs.at(index); + if (!output) { + throw new Error(`Missing output for oxlint ${leg.label}`); + } + if (isTransientFailure(output.stdout.toString(), output.exitCode)) { + const how = `exit ${output.exitCode}, ${producedNoJSON(output.stdout.toString()) ? 'no JSON' : 'JSON present'}`; + output = await this.runOxlint(leg.args); + const recovered = !isTransientFailure(output.stdout.toString(), output.exitCode); + notes.push(`Oxlint ${leg.label} died (${how}) and was retried once, serially: ${recovered ? 'recovered' : 'failed again'}.`); + } + const result = parseOxlintStdout(output.stdout.toString(), output.stderr.toString(), output.exitCode, this.options.projectRoot, leg.lintedFiles); + parsed.push(result.exitCode >= FATAL_EXIT_CODE ? {...result, stderr: `Oxlint ${leg.label}:\n${result.stderr}`} : result); + } + return mergeShardResults(parsed); + } + + async run(targets: string[]): Promise { + const {files: lintedFiles, retried: listerRetried} = await this.listLintedFilesWithRetry(targets); + if (lintedFiles.length === 0) { + return fatal(`Oxlint matched no files for: ${targets.join(' ')} (asked twice, in case the first listing died)`, '', '', FATAL_EXIT_CODE); + } + + const notes: string[] = []; + if (listerRetried) { + notes.push(`Oxlint listed no files on the first attempt and ${lintedFiles.length} on the second, so that listing died rather than matching nothing.`); + } + + const baseArgs: string[] = ['--format', 'json']; + if (this.options.fix) { + baseArgs.push('--fix'); + } + + const shardCount = resolveShardCount(this.options.shards ?? process.env.OXLINT_SHARDS); + let plan: string; + let merged: LinterResult; + try { + if (shardCount <= 1) { + const threads = this.options.threads ?? process.env.OXLINT_THREADS; + const args = threads ? [`--threads=${threads}`, ...baseArgs] : baseArgs; + plan = 'Oxlint plan: 1 process, stock config.'; + merged = await this.runLegs([{label: 'process', args: [...args, ...lintedFiles], lintedFiles}], notes); + } else { + const buckets = shardFiles(lintedFiles, shardCount); + plan = `Oxlint plan: ${buckets.length} JS-plugin shards plus 1 type-aware process (cores ${os.availableParallelism()}, available memory ${(availableMemoryBytes() / GIB).toFixed(1)} GB).`; + const configs = await this.writeLegConfigs(); + try { + const legs: OxlintLeg[] = buckets.map((bucket, index) => ({ + label: `JS-plugin shard ${index + 1} of ${buckets.length}`, + args: ['-c', configs.jsPluginsConfig, '--threads=1', ...baseArgs, ...bucket], + lintedFiles: bucket, + })); + legs.push({ + label: 'type-aware process', + args: ['-c', configs.typeAwareConfig, ...baseArgs, ...targets], + lintedFiles, + }); + merged = await this.runLegs(legs, notes); + } finally { + configs.remove(); + } + } + } catch (error) { + return fatal(error instanceof Error ? error.message : String(error), '', '', FATAL_EXIT_CODE); + } + + if (notes.length === 0 && merged.exitCode < FATAL_EXIT_CODE) { + return merged; + } + return { + ...merged, + stderr: [merged.stderr, ...notes, plan].filter(Boolean).join('\n'), + }; + } +} + +export default OxlintLinter; +export { + defaultShardCount, + deriveLegConfigs, + extractJSONObject, + isOxlintConfig, + isTransientFailure, + joinDiagnosticText, + jsPluginName, + loadOxlintConfig, + mergeShardResults, + normalizeOxlintDiagnostics, + parseOxlintStdout, + producedNoJSON, + resolveShardCount, + shardFiles, +}; +export type {OxlintConfig, OxlintDiagnostic, OxlintLinterOptions}; diff --git a/scripts/lint/processors/ReactCompilerFilter.ts b/scripts/lint/processors/ReactCompilerFilter.ts index 334a9ce92a4e..0ba65204cfe1 100644 --- a/scripts/lint/processors/ReactCompilerFilter.ts +++ b/scripts/lint/processors/ReactCompilerFilter.ts @@ -3,11 +3,11 @@ import path from 'node:path'; import type {LintMessage, ProcessorContext} from '../types'; +// eslint-disable-next-line import/extensions +import {EXHAUSTIVE_DEPS_USECALLBACK_USEMEMO_PATTERN, RULES_SUPPRESSED_BY_REACT_COMPILER} from '../../../config/reactCompiler/suppressedRules.mjs'; import WorkerPool from '../../utils/WorkerPool'; import Processor from '../Processor'; -const RULES_SUPPRESSED_BY_REACT_COMPILER = new Set(['react/jsx-no-constructed-context-values', 'rulesdir/no-inline-useOnyx-selector']); -const EXHAUSTIVE_DEPS_USECALLBACK_USEMEMO_PATTERN = /\buseCallback\(\) Hook\b|\buseMemo\(\) Hook\b/; const CACHE_DIR = 'node_modules/.cache/react-compiler'; const REACT_COMPILER_FINGERPRINT_FILES = [ 'babel.config.js', diff --git a/scripts/lint/processors/Seatbelt.ts b/scripts/lint/processors/Seatbelt.ts index 5beddb43b98a..d521d8d1f4df 100644 --- a/scripts/lint/processors/Seatbelt.ts +++ b/scripts/lint/processors/Seatbelt.ts @@ -9,7 +9,12 @@ import Processor from '../Processor'; import {LINT_SEVERITY} from '../types'; const SEATBELT_NAME = 'eslint-seatbelt'; -const SEATBELT_TSV_RELATIVE = 'config/eslint/eslint.seatbelt.tsv'; +const SEATBELT_TSV_BY_LINTER = { + eslint: 'config/eslint/eslint.seatbelt.tsv', + oxlint: 'config/oxlint/oxlint.seatbelt.tsv', +} as const; + +const SEATBELT_TSV_RELATIVE = SEATBELT_TSV_BY_LINTER.eslint; const DEFAULT_FILE_HEADER = `# ${SEATBELT_NAME} temporarily allowed errors # docs: https://github.com/justjake/${SEATBELT_NAME}#readme`; @@ -476,7 +481,8 @@ async function applySeatbelt(messages: LintMessage[], options: SeatbeltOptions, const shouldWrite = anyChanged && !options.frozen && !options.readOnly; if (pruned > 0) { const verb = shouldWrite ? 'removed' : 'would remove'; - console.log(`eslint-seatbelt: ${verb} ${pruned} baseline row(s) for deleted files`); + // stderr, not stdout: --format=json writes the report to stdout and this would corrupt it. + console.error(`eslint-seatbelt: ${verb} ${pruned} baseline row(s) for deleted files`); } if (shouldWrite) { await writeTSVAtomically(options.seatbeltFile, tsv); @@ -519,11 +525,11 @@ function parseRuleSetEnvVar(value: string | undefined): SeatbeltRuleSet | undefi * - `SEATBELT_FROZEN` defaults to false (the wrapper forces `0` so `CI=true` does not freeze). * - `readOnly` defaults to `!CI`; `SEATBELT_INCREASE` forces writes. */ -function resolveSeatbeltOptions(projectRoot: string, env: NodeJS.ProcessEnv = process.env): SeatbeltOptions { +function resolveSeatbeltOptions(projectRoot: string, env: NodeJS.ProcessEnv = process.env, seatbeltRelativePath: string = SEATBELT_TSV_RELATIVE): SeatbeltOptions { const allowIncreaseRules = parseRuleSetEnvVar(env.SEATBELT_INCREASE) ?? new Set(); const isIncreaseSet = allowIncreaseRules === 'all' || allowIncreaseRules.size > 0; return { - seatbeltFile: `${projectRoot}/${SEATBELT_TSV_RELATIVE}`, + seatbeltFile: `${projectRoot}/${seatbeltRelativePath}`, projectRoot, disable: readBooleanEnvVar(env.SEATBELT_DISABLE) ?? false, frozen: readBooleanEnvVar(env.SEATBELT_FROZEN) ?? false, @@ -536,5 +542,16 @@ function resolveSeatbeltOptions(projectRoot: string, env: NodeJS.ProcessEnv = pr } export default Seatbelt; -export {applySeatbelt, canonicalizeMessages, compareMessages, countRuleIDs, parseSeatbeltTSV, resolveSeatbeltOptions, serializeSeatbeltTSV, transformMessages, updateMaxErrors}; +export { + SEATBELT_TSV_BY_LINTER, + applySeatbelt, + canonicalizeMessages, + compareMessages, + countRuleIDs, + parseSeatbeltTSV, + resolveSeatbeltOptions, + serializeSeatbeltTSV, + transformMessages, + updateMaxErrors, +}; export type {SeatbeltApplyResult, SeatbeltFileData}; diff --git a/tests/tooling/lintPipeline.test.ts b/tests/tooling/lintPipeline.test.ts index c8785733fc22..c47c99a1eabe 100644 --- a/tests/tooling/lintPipeline.test.ts +++ b/tests/tooling/lintPipeline.test.ts @@ -1,15 +1,34 @@ import {describe, expect, it} from 'bun:test'; +import fs from 'node:fs'; +import path from 'node:path'; + import type {ESLintJSONResult} from '../../scripts/lint/eslint/ESLintLinter'; +import type {OxlintConfig, OxlintDiagnostic} from '../../scripts/lint/oxlint/OxlintLinter'; import type {LintMessage, LinterResult} from '../../scripts/lint/types'; +import {oxlintCodeToESLintRuleID} from '../../config/oxlint/ruleNames.mjs'; import {normalizeESLintResults, parseESLintStdout} from '../../scripts/lint/eslint/ESLintLinter'; +import JSONFormatter from '../../scripts/lint/formatters/JSONFormatter'; import StylishFormatter from '../../scripts/lint/formatters/StylishFormatter'; import Linter from '../../scripts/lint/Linter'; import Pipeline from '../../scripts/lint/LintPipeline'; +import { + defaultShardCount, + deriveLegConfigs, + isTransientFailure, + jsPluginName, + loadOxlintConfig, + mergeShardResults, + normalizeOxlintDiagnostics, + parseOxlintStdout, + producedNoJSON, + resolveShardCount, + shardFiles, +} from '../../scripts/lint/oxlint/OxlintLinter'; import {filterReactCompilerMessages, shouldPersistCompilerCache} from '../../scripts/lint/processors/ReactCompilerFilter'; -import Seatbelt, {resolveSeatbeltOptions} from '../../scripts/lint/processors/Seatbelt'; -import {stratifyMessages} from '../../scripts/lint/processors/StratifyNoDeprecated'; +import Seatbelt, {SEATBELT_TSV_BY_LINTER, parseSeatbeltTSV, resolveSeatbeltOptions} from '../../scripts/lint/processors/Seatbelt'; +import {NO_DEPRECATED_RULE_ID, stratifyMessages} from '../../scripts/lint/processors/StratifyNoDeprecated'; import {LINT_SEVERITY} from '../../scripts/lint/types'; function makeMessage(overrides: Partial = {}): LintMessage { @@ -51,17 +70,28 @@ describe('resolveSeatbeltOptions', () => { }); it('lets SEATBELT_INCREASE force writes even locally', () => { - const options = resolveSeatbeltOptions(root, {SEATBELT_INCREASE: 'no-console'}); + const options = resolveSeatbeltOptions(root, { + SEATBELT_INCREASE: 'no-console', + }); expect(options.readOnly).toBe(false); expect(options.allowIncreaseRules).toEqual(new Set(['no-console'])); }); it('lets SEATBELT_INCREASE override SEATBELT_READ_ONLY', () => { - const options = resolveSeatbeltOptions(root, {SEATBELT_INCREASE: 'no-console', SEATBELT_READ_ONLY: '1'}); + const options = resolveSeatbeltOptions(root, { + SEATBELT_INCREASE: 'no-console', + SEATBELT_READ_ONLY: '1', + }); expect(options.readOnly).toBe(false); expect(options.allowIncreaseRules).toEqual(new Set(['no-console'])); }); + it('defaults to the ESLint baseline and takes a per-linter override', () => { + expect(resolveSeatbeltOptions(root, {}).seatbeltFile).toBe(`${root}/${SEATBELT_TSV_BY_LINTER.eslint}`); + expect(resolveSeatbeltOptions(root, {}, SEATBELT_TSV_BY_LINTER.oxlint).seatbeltFile).toBe(`${root}/${SEATBELT_TSV_BY_LINTER.oxlint}`); + expect(SEATBELT_TSV_BY_LINTER.eslint).not.toBe(SEATBELT_TSV_BY_LINTER.oxlint); + }); + it('parses SEATBELT_INCREASE=ALL', () => { expect(resolveSeatbeltOptions(root, {SEATBELT_INCREASE: 'ALL'}).allowIncreaseRules).toBe('all'); }); @@ -76,7 +106,18 @@ describe('resolveSeatbeltOptions', () => { describe('extractJSONArray via runESLint stdout', () => { it('normalizes results even when babel logs wrap the JSON array', () => { const wrapped = `babel.config.js\n - running in: undefined\n${JSON.stringify([ - {filePath: '/repo/src/a.ts', messages: [{ruleId: 'no-console', severity: 2, message: 'nope', line: 3, column: 4}]}, + { + filePath: '/repo/src/a.ts', + messages: [ + { + ruleId: 'no-console', + severity: 2, + message: 'nope', + line: 3, + column: 4, + }, + ], + }, ])}\n`; const start = wrapped.indexOf('['); const end = wrapped.lastIndexOf(']'); @@ -98,7 +139,15 @@ describe('normalizeESLintResults', () => { const [result] = normalizeESLintResults([ { filePath: '/repo/src/a.ts', - messages: [{ruleId: 'no-console', severity: 2, message: 'nope', line: 3, column: 4}], + messages: [ + { + ruleId: 'no-console', + severity: 2, + message: 'nope', + line: 3, + column: 4, + }, + ], }, ]); expect(result.filePath).toBe('/repo/src/a.ts'); @@ -134,7 +183,10 @@ describe('filterReactCompilerMessages', () => { it('drops suppressible messages when both compilers memoize the file', async () => { const messages = [ makeMessage({ruleID: 'react/jsx-no-constructed-context-values'}), - makeMessage({ruleID: 'react-hooks/exhaustive-deps', message: 'React Hook useCallback() Hook is missing a dependency'}), + makeMessage({ + ruleID: 'react-hooks/exhaustive-deps', + message: 'React Hook useCallback() Hook is missing a dependency', + }), makeMessage({ruleID: 'no-console'}), ]; const result = await filterReactCompilerMessages(messages, '/tmp', () => true); @@ -148,7 +200,12 @@ describe('filterReactCompilerMessages', () => { }); it('does not suppress genuine exhaustive-deps missing-deps warnings', async () => { - const messages = [makeMessage({ruleID: 'react-hooks/exhaustive-deps', message: 'React Hook useEffect has a missing dependency: "foo"'})]; + const messages = [ + makeMessage({ + ruleID: 'react-hooks/exhaustive-deps', + message: 'React Hook useEffect has a missing dependency: "foo"', + }), + ]; const result = await filterReactCompilerMessages(messages, '/tmp', () => true); expect(result).toEqual(messages); }); @@ -162,15 +219,38 @@ describe('filterReactCompilerMessages', () => { }); it('keeps suppressible messages when the source file cannot be read', async () => { - const messages = [makeMessage({filePath: '/tmp/does-not-exist.tsx', ruleID: 'react/jsx-no-constructed-context-values'})]; + const messages = [ + makeMessage({ + filePath: '/tmp/does-not-exist.tsx', + ruleID: 'react/jsx-no-constructed-context-values', + }), + ]; const result = await filterReactCompilerMessages(messages, '/tmp'); expect(result).toEqual(messages); }); it('does not persist fallback compiler failures to cache', () => { - expect(shouldPersistCompilerCache({filename: 'a.tsx', bothMemoized: false, cacheable: false})).toBe(false); - expect(shouldPersistCompilerCache({filename: 'a.tsx', bothMemoized: false, cacheable: true})).toBe(true); - expect(shouldPersistCompilerCache({filename: 'a.tsx', bothMemoized: true, cacheable: true})).toBe(true); + expect( + shouldPersistCompilerCache({ + filename: 'a.tsx', + bothMemoized: false, + cacheable: false, + }), + ).toBe(false); + expect( + shouldPersistCompilerCache({ + filename: 'a.tsx', + bothMemoized: false, + cacheable: true, + }), + ).toBe(true); + expect( + shouldPersistCompilerCache({ + filename: 'a.tsx', + bothMemoized: true, + cacheable: true, + }), + ).toBe(true); expect(shouldPersistCompilerCache(undefined)).toBe(false); }); }); @@ -178,13 +258,25 @@ describe('filterReactCompilerMessages', () => { describe('stratifyMessages', () => { it('rewrites no-deprecated using the source expression at the lint location', () => { const source = 'const x = StyleSheet.absoluteFillObject;\n'; - const messages = [makeMessage({ruleID: '@typescript-eslint/no-deprecated', message: '`absoluteFillObject` is deprecated.', line: 1, column: 11})]; + const messages = [ + makeMessage({ + ruleID: '@typescript-eslint/no-deprecated', + message: '`absoluteFillObject` is deprecated.', + line: 1, + column: 11, + }), + ]; const result = stratifyMessages(messages, source); expect(result.at(0)?.ruleID).toBe('@typescript-eslint/no-deprecated/StyleSheet.absoluteFillObject'); }); it('falls back to the backtick symbol in the message when there is no source', () => { - const messages = [makeMessage({ruleID: '@typescript-eslint/no-deprecated', message: '`Foo.bar` is deprecated.'})]; + const messages = [ + makeMessage({ + ruleID: '@typescript-eslint/no-deprecated', + message: '`Foo.bar` is deprecated.', + }), + ]; expect(stratifyMessages(messages, null).at(0)?.ruleID).toBe('@typescript-eslint/no-deprecated/Foo.bar'); }); @@ -194,6 +286,351 @@ describe('stratifyMessages', () => { }); }); +function oxlintDiagnostic(overrides: Partial = {}): OxlintDiagnostic { + return { + message: 'nope', + code: 'eslint(no-console)', + severity: 'error', + filename: 'src/a.ts', + labels: [{span: {offset: 42, length: 7, line: 3, column: 4}}], + ...overrides, + }; +} + +function oxlintStdout(diagnostics: OxlintDiagnostic[], numberOfFiles = 1, prefix = ''): string { + return `${prefix}{ "diagnostics": ${JSON.stringify(diagnostics)},\n "number_of_files": ${numberOfFiles},\n "number_of_rules": 475\n}\n`; +} + +describe('normalizeOxlintDiagnostics', () => { + it('maps every plugin prefix the repo produces to its ESLint rule id', () => { + const expected: Array<[string, string]> = [ + ['typescript(no-unsafe-type-assertion)', '@typescript-eslint/no-unsafe-type-assertion'], + ['import(no-cycle)', 'import/no-cycle'], + ['rc(set-state-in-effect)', 'react-hooks/set-state-in-effect'], + ['core(no-restricted-syntax)', 'no-restricted-syntax'], + ['rulesdir(no-onyx-connect)', 'rulesdir/no-onyx-connect'], + ['unicorn(prefer-at)', 'unicorn/prefer-at'], + ['eslint(no-restricted-imports)', 'no-restricted-imports'], + ['react(jsx-key)', 'react/jsx-key'], + ['hosted(naming-convention)', '@typescript-eslint/naming-convention'], + ['eslint(no-object-constructor)', 'no-new-object'], + ['react(exhaustive-deps)', 'react-hooks/exhaustive-deps'], + ]; + const diagnostics = expected.map(([code]) => oxlintDiagnostic({code})); + const [file] = normalizeOxlintDiagnostics(diagnostics, '/repo', ['src/a.ts']); + expect(file.messages.map((message) => message.ruleID)).toEqual(expected.map(([, ruleID]) => ruleID)); + }); + + it('resolves oxlint cwd-relative paths against the project root', () => { + const [file] = normalizeOxlintDiagnostics([oxlintDiagnostic()], '/repo', ['src/a.ts']); + expect(file.filePath).toBe('/repo/src/a.ts'); + expect(file.messages.at(0)?.filePath).toBe('/repo/src/a.ts'); + }); + + it('keeps the linted files that produced no diagnostic, so the seatbelt can ratchet down', () => { + const files = normalizeOxlintDiagnostics([oxlintDiagnostic()], '/repo', ['src/a.ts', 'src/clean.ts']); + expect(files.map((file) => file.filePath)).toEqual(['/repo/src/a.ts', '/repo/src/clean.ts']); + expect(files.at(1)?.messages).toEqual([]); + }); + + it('passes 1-based line and column through, and defaults a label-less diagnostic to 0', () => { + const [file] = normalizeOxlintDiagnostics([oxlintDiagnostic(), oxlintDiagnostic({labels: []})], '/repo', ['src/a.ts']); + expect(file.messages.at(0)).toMatchObject({line: 3, column: 4}); + expect(file.messages.at(1)).toMatchObject({line: 0, column: 0}); + }); + + it('folds help and note into the message, which is the only text field downstream reads', () => { + const diagnostic = oxlintDiagnostic({ + message: 'Dependency cycle detected', + help: 'Refactor to remove the cycle.', + note: 'These paths form a cycle:\nA -> B -> A', + }); + const [file] = normalizeOxlintDiagnostics([diagnostic], '/repo', ['src/a.ts']); + expect(file.messages.at(0)?.message).toBe('Dependency cycle detected\nRefactor to remove the cycle.\nThese paths form a cycle:\nA -> B -> A'); + }); + + it('demotes oxlint warning and advice severities, and treats everything else as an error', () => { + const diagnostics = [oxlintDiagnostic({severity: 'warning'}), oxlintDiagnostic({severity: 'advice'}), oxlintDiagnostic({severity: 'error'})]; + const [file] = normalizeOxlintDiagnostics(diagnostics, '/repo', ['src/a.ts']); + expect(file.messages.map((message) => message.severity)).toEqual([LINT_SEVERITY.WARNING, LINT_SEVERITY.WARNING, LINT_SEVERITY.ERROR]); + }); + + it('throws on a hosted rule with no known ESLint origin rather than emitting an unmatchable id', () => { + expect(() => normalizeOxlintDiagnostics([oxlintDiagnostic({code: 'hosted(not-a-hosted-rule)'})], '/repo', ['src/a.ts'])).toThrow(/not-a-hosted-rule/); + }); +}); + +describe('parseOxlintStdout', () => { + it('parses the report even when oxlint prints a warning to stdout ahead of it', () => { + const parsed = parseOxlintStdout(oxlintStdout([oxlintDiagnostic()], 1, 'No files found to lint. Please check your paths and ignore patterns.\n'), '', 1, '/repo', ['src/a.ts']); + expect(parsed.exitCode).toBe(1); + expect(parsed.files.at(0)?.messages.at(0)?.ruleID).toBe('no-console'); + }); + + it('treats a config that failed to parse as fatal, even though oxlint exits 1', () => { + const configError = "Failed to parse oxlint configuration file.\n\n x Rule 'nope' not found in plugin 'eslint'\n"; + const parsed = parseOxlintStdout(configError, '', 1, '/repo', ['src/a.ts']); + expect(parsed.exitCode).toBe(2); + expect(parsed.files).toEqual([]); + expect(parsed.stderr).toContain('Failed to parse Oxlint JSON output'); + }); + + it('treats a diagnostic with no rule code as a crashing JS plugin, not a finding', () => { + const parsed = parseOxlintStdout(oxlintStdout([oxlintDiagnostic({code: undefined, message: 'plugin blew up'})]), '', 1, '/repo', ['src/a.ts']); + expect(parsed.exitCode).toBe(2); + expect(parsed.files).toEqual([]); + expect(parsed.stderr).toContain('no rule code'); + expect(parsed.stderr).toContain('plugin blew up'); + }); + + it('rejects a JSON object that is not an oxlint report', () => { + expect(parseOxlintStdout('{"oops": true}', '', 1, '/repo', []).exitCode).toBe(2); + }); + + it('preserves an oxlint crash exit code above 2', () => { + expect(parseOxlintStdout('', 'oom', 137, '/repo', []).exitCode).toBe(137); + }); + + it('surfaces an Oxlint fatal through the pipeline the same way an ESLint one does', async () => { + const parsed = parseOxlintStdout('Failed to parse oxlint configuration file.', '', 1, '/repo', []); + const pipeline = new Pipeline('/tmp', new StubLinter(parsed), [new Seatbelt(resolveSeatbeltOptions('/tmp', {SEATBELT_DISABLE: '1'}))], new StylishFormatter('/tmp', false)); + const result = await pipeline.run(['.']); + expect(result.exitCode).toBe(2); + expect(result.reportText).toContain('Failed to parse Oxlint JSON output'); + }); +}); + +describe('oxlint sharding', () => { + function shardResult(filePath: string, exitCode: number, messages: LintMessage[] = []): LinterResult { + return {files: [{filePath, messages}], exitCode, stderr: ''}; + } + + it('interleaves files across shards so adjacent paths land in different buckets', () => { + const files = Array.from({length: 10}, (_, i) => `src/f${i}.ts`); + const shards = shardFiles(files, 3); + expect(shards.map((shard) => shard.length)).toEqual([4, 3, 3]); + expect(shards.at(0)).toEqual(['src/f0.ts', 'src/f3.ts', 'src/f6.ts', 'src/f9.ts']); + expect(shards.flat().sort()).toEqual([...files].sort()); + }); + + it('a shard count of 1 or more than the file count never drops or duplicates a file', () => { + const files = ['a.ts', 'b.ts', 'c.ts']; + expect(shardFiles(files, 1)).toEqual([files]); + expect(shardFiles(files, 99).flat().sort()).toEqual(files); + expect(shardFiles(files, 99)).toHaveLength(3); + }); + + it('auto-shards by machine size and honours explicit overrides', () => { + expect(resolveShardCount(undefined)).toBeGreaterThanOrEqual(1); + expect(resolveShardCount('')).toBeGreaterThanOrEqual(1); + expect(resolveShardCount('6')).toBe(6); + expect(resolveShardCount('0')).toBe(1); + expect(resolveShardCount('not-a-number')).toBe(1); + }); + + it('sizes the JS-plugin shards by half the cores, minus the memory one type-aware process needs', () => { + const gib = 1073741824; + expect(defaultShardCount(14, 48 * gib)).toBe(7); + expect(defaultShardCount(14, 18 * gib)).toBe(4); + expect(defaultShardCount(4, 16 * gib)).toBe(2); + expect(defaultShardCount(4, 4 * gib)).toBe(1); + expect(defaultShardCount(1, 64 * gib)).toBe(1); + }); + + it('derives the rule prefix of a jsPlugins entry the way oxlint does', () => { + expect( + jsPluginName({ + name: 'rulesdir', + specifier: './config/oxlint/plugins/expensify-rules.mjs', + }), + ).toBe('rulesdir'); + expect(jsPluginName('eslint-plugin-lodash')).toBe('lodash'); + expect(jsPluginName('@dword-design/eslint-plugin-import-alias')).toBe('@dword-design/import-alias'); + expect(jsPluginName('@scope/eslint-plugin')).toBe('@scope'); + }); + + /* eslint-disable @typescript-eslint/naming-convention */ + it('splits the config into a JS-plugin leg without type information and a type-aware leg without JS plugins', () => { + const config: OxlintConfig = { + plugins: ['typescript', 'import'], + jsPlugins: [{name: 'rulesdir', specifier: './plugins/rules.mjs'}, 'eslint-plugin-lodash'], + options: {typeAware: true}, + ignorePatterns: ['dist/**'], + rules: { + 'no-console': 'error', + 'typescript/no-floating-promises': 'error', + 'rulesdir/no-onyx-connect': 'error', + 'lodash/import-scope': ['error', 'method'], + }, + overrides: [ + { + files: ['**/*.test.ts'], + jsPlugins: ['@dword-design/eslint-plugin-import-alias'], + rules: { + '@dword-design/import-alias/prefer-alias': 'error', + 'no-console': 'off', + }, + }, + {files: ['**/*.js'], rules: {'rulesdir/no-onyx-connect': 'off'}}, + ], + }; + const {jsPlugins, typeAware} = deriveLegConfigs(config); + + expect(jsPlugins.options).toEqual({typeAware: false}); + expect(jsPlugins.jsPlugins).toEqual(config.jsPlugins); + expect(jsPlugins.rules).toEqual(config.rules); + expect(jsPlugins.overrides).toEqual(config.overrides); + + expect(typeAware.options).toEqual({typeAware: true}); + expect(typeAware).not.toHaveProperty('jsPlugins'); + expect(typeAware.rules).toEqual({ + 'no-console': 'error', + 'typescript/no-floating-promises': 'error', + }); + expect(typeAware.overrides).toEqual([ + {files: ['**/*.test.ts'], rules: {'no-console': 'off'}}, + {files: ['**/*.js'], rules: {}}, + ]); + expect(typeAware.overrides?.at(0)).not.toHaveProperty('jsPlugins'); + + expect(typeAware.ignorePatterns).toEqual(['dist/**']); + expect(typeAware.plugins).toEqual(['typescript', 'import']); + expect(config.jsPlugins).toHaveLength(2); + expect(config.options).toEqual({typeAware: true}); + }); + /* eslint-enable @typescript-eslint/naming-convention */ + + it('drops a finding a later leg repeats for the same file, but keeps one a single leg reports twice', () => { + const native = makeMessage({ruleID: 'no-console', line: 3, column: 4}); + const typeAwareOnly = makeMessage({ + ruleID: '@typescript-eslint/no-floating-promises', + line: 9, + column: 1, + }); + const twiceInOneLeg = makeMessage({ + ruleID: 'react-hooks/refs', + line: 12, + column: 5, + }); + const merged = mergeShardResults([shardResult('/repo/a.ts', 1, [native, twiceInOneLeg, twiceInOneLeg]), shardResult('/repo/a.ts', 1, [native, typeAwareOnly])]); + expect(merged.files).toHaveLength(1); + expect(merged.files.at(0)?.messages).toEqual([native, twiceInOneLeg, twiceInOneLeg, typeAwareOnly]); + }); + + it('retries a leg that died, not one that failed deterministically', () => { + expect(isTransientFailure('', 137)).toBe(true); + expect(isTransientFailure('Error running tsgolint: "exit status: exit status: 1"\n', 1)).toBe(true); + expect(isTransientFailure(oxlintStdout([]), 137)).toBe(true); + expect(isTransientFailure(oxlintStdout([]), 134)).toBe(true); + expect(isTransientFailure(oxlintStdout([oxlintDiagnostic({code: undefined, message: 'plugin blew up'})]), 1)).toBe(false); + expect(isTransientFailure(oxlintStdout([oxlintDiagnostic()], 1), 1)).toBe(false); + expect(isTransientFailure(oxlintStdout([]), 0)).toBe(false); + }); + + it('merges shards by concatenating files and taking the worst exit code', () => { + const merged = mergeShardResults([shardResult('/repo/a.ts', 0, [makeMessage()]), shardResult('/repo/b.ts', 1, [makeMessage()]), shardResult('/repo/c.ts', 0)]); + expect(merged.exitCode).toBe(1); + expect(merged.files.map((file) => file.filePath)).toEqual(['/repo/a.ts', '/repo/b.ts', '/repo/c.ts']); + }); + + it('a crashed JS plugin in one shard is fatal and dominates clean shards', () => { + const clean = parseOxlintStdout(oxlintStdout([oxlintDiagnostic()], 1), '', 1, '/repo', ['src/a.ts']); + const crash = parseOxlintStdout(oxlintStdout([oxlintDiagnostic({code: undefined, message: 'plugin blew up'})]), '', 1, '/repo', ['src/b.ts']); + const merged = mergeShardResults([clean, crash]); + expect(merged.exitCode).toBe(2); + expect(merged.stderr).toContain('plugin blew up'); + }); + + it('only a shard that produced no JSON is worth retrying', () => { + // A killed process writes nothing, and a config error prints its complaint without JSON. + expect(producedNoJSON('')).toBe(true); + expect(producedNoJSON('Failed to parse oxlint configuration file.\n x Rule not found\n')).toBe(true); + + // Deterministic failures are not retried: both of these carry a JSON body. + expect(producedNoJSON(oxlintStdout([oxlintDiagnostic({code: undefined, message: 'plugin blew up'})]))).toBe(false); + expect(producedNoJSON(oxlintStdout([oxlintDiagnostic()], 1))).toBe(false); + expect(producedNoJSON(oxlintStdout([]))).toBe(false); + + // Oxlint prints warnings ahead of the JSON, which must not read as a dead shard. + expect(producedNoJSON(`No files found to lint.\n${oxlintStdout([])}`)).toBe(false); + }); +}); + +describe('oxlint rule names', () => { + it('every rule id in the oxlint seatbelt is one the enabled config still produces through the mapping', async () => { + const root = path.join(import.meta.dir, '..', '..'); + const config = await loadOxlintConfig(root); + const configuredRules = [config.rules, ...(config.overrides ?? []).map((override) => override.rules)].flatMap((rules) => Object.keys(rules ?? {})); + const toDiagnosticCode = (rule: string) => { + const slash = rule.lastIndexOf('/'); + return slash < 0 ? `eslint(${rule})` : `${rule.slice(0, slash).replace('jsx-a11y', 'jsx_a11y')}(${rule.slice(slash + 1)})`; + }; + const reachable = new Set(configuredRules.map((rule) => oxlintCodeToESLintRuleID(toDiagnosticCode(rule)))); + + const {data} = parseSeatbeltTSV(fs.readFileSync(path.join(root, SEATBELT_TSV_BY_LINTER.oxlint), 'utf8')); + const seatbeltRuleIDs = new Set( + [...data.values()].flatMap((file) => file.lines.map((line) => (line.ruleID.startsWith(`${NO_DEPRECATED_RULE_ID}/`) ? NO_DEPRECATED_RULE_ID : line.ruleID))), + ); + + expect(seatbeltRuleIDs.size).toBeGreaterThan(20); + expect([...seatbeltRuleIDs].filter((ruleID) => !reachable.has(ruleID))).toEqual([]); + }); +}); + +function knownOxlintPlugins(schema: unknown): string[] { + if (typeof schema !== 'object' || schema === null || !('definitions' in schema)) { + return []; + } + const definitions: unknown = schema.definitions; + if (typeof definitions !== 'object' || definitions === null || !('LintPluginOptionsSchema' in definitions)) { + return []; + } + const pluginSchema: unknown = definitions.LintPluginOptionsSchema; + if (typeof pluginSchema !== 'object' || pluginSchema === null || !('enum' in pluginSchema) || !Array.isArray(pluginSchema.enum)) { + return []; + } + return pluginSchema.enum.filter((entry): entry is string => typeof entry === 'string'); +} + +describe('oxlint config', () => { + it('names only plugins oxlint knows, at the root and in every override', async () => { + // An unknown name in an override's `plugins` array is not an error: the override's other rules + // keep working while the rules the missing plugin owns report nothing. + const root = path.join(import.meta.dir, '..', '..'); + const known = new Set(knownOxlintPlugins(JSON.parse(fs.readFileSync(path.join(root, 'node_modules/oxlint/configuration_schema.json'), 'utf8')))); + expect(known.size).toBeGreaterThan(5); + + const config = await loadOxlintConfig(root); + const named = [config.plugins, ...(config.overrides ?? []).map((override) => override.plugins)].flatMap((plugins: unknown) => + Array.isArray(plugins) ? plugins.map((entry: unknown) => entry) : [], + ); + expect(named.filter((plugin) => typeof plugin !== 'string' || !known.has(plugin))).toEqual([]); + }); +}); + +describe('JSONFormatter', () => { + it('emits the linter-agnostic message list, warnings included', () => { + const messages = [makeMessage(), makeMessage({severity: LINT_SEVERITY.WARNING, ruleID: 'no-debugger'})]; + const result = new JSONFormatter().format(messages); + expect(JSON.parse(result.text)).toEqual({ + messages, + errorCount: 1, + warningCount: 1, + }); + expect(result.errorCount).toBe(1); + expect(result.warningCount).toBe(1); + }); + + it('emits an empty report that still parses, rather than an empty string', () => { + const result = new JSONFormatter().format([]); + expect(JSON.parse(result.text)).toEqual({ + messages: [], + errorCount: 0, + warningCount: 0, + }); + }); +}); + describe('Pipeline', () => { it('returns the linter exit code when the linter itself crashed', async () => { const pipeline = new Pipeline( diff --git a/tsconfig.node.json b/tsconfig.node.json index 7896af84ad2a..706940424424 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -9,6 +9,6 @@ // `prompts` is deliberately not listed even though the Proposal Police action imports it: the Bun // program owns those files, and listing them in two referenced projects would give editors two // candidate owners for one file. The action's imports still pull them into this program. - "include": [".github/**/*", "web/proxy.ts", "config/**/*", "config/**/*.d.mts"], + "include": [".github/**/*", "web/proxy.ts", "config/**/*", "config/**/*.d.mts", "oxlint.config.mts"], "exclude": ["**/node_modules/*", "**/dist/*", ".github/actions/**/index.js"] } From f378b726cf8a810c243696a7652a479bb56df52b Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Wed, 21 Oct 2026 13:15:35 +0200 Subject: [PATCH 02/37] Re-baseline the oxlint seatbelt against current main Main added files that trip the type-aware rules (NumericInput primitives, applyColumnWidthOverrides) and fixed the Gesture.Pan deprecations, so re-measure the baseline: 7 findings added, 8 stale entries dropped. --- config/oxlint/oxlint.seatbelt.tsv | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/config/oxlint/oxlint.seatbelt.tsv b/config/oxlint/oxlint.seatbelt.tsv index 4831d1a255ae..fa536ee494ee 100644 --- a/config/oxlint/oxlint.seatbelt.tsv +++ b/config/oxlint/oxlint.seatbelt.tsv @@ -215,7 +215,6 @@ "../../src/components/HTMLEngineProvider/HTMLRenderers/ConciergeLinkRenderer.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/HTMLEngineProvider/HTMLRenderers/EditedRenderer.tsx" "@typescript-eslint/no-deprecated/fontSize" 3 "../../src/components/HTMLEngineProvider/HTMLRenderers/EmojiRenderer.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 -"../../src/components/HTMLEngineProvider/HTMLRenderers/HTMLTableScroll/index.native.tsx" "@typescript-eslint/no-deprecated/Gesture.Pan" 2 "../../src/components/HTMLEngineProvider/HTMLRenderers/index.ts" "import/no-cycle" 2 "../../src/components/HTMLEngineProvider/HTMLRenderers/MentionHereRenderer.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 @@ -268,8 +267,6 @@ "../../src/components/Modal/BaseModal.tsx" "react-hooks/set-state-in-effect" 1 "../../src/components/Modal/Global/ConfirmModalWrapper.tsx" "@typescript-eslint/no-deprecated/ConfirmModal" 1 "../../src/components/Modal/Global/ModalContext.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 -"../../src/components/Modal/ReanimatedModal/Container/GestureHandler.tsx" "@typescript-eslint/no-deprecated/Gesture.Pan" 2 -"../../src/components/Modal/ReanimatedModal/Container/GestureHandler.tsx" "@typescript-eslint/no-deprecated/GestureType" 1 "../../src/components/Modal/ReanimatedModal/Container/index.web.tsx" "react-hooks/refs" 1 "../../src/components/MoneyReportHeader.tsx" "no-restricted-imports" 1 "../../src/components/MoneyReportHeaderActions/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 @@ -321,6 +318,8 @@ "../../src/components/Navigation/QuickCreationActionsBar/index.tsx" "no-restricted-imports" 1 "../../src/components/NumberWithSymbolForm.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/NumberWithSymbolForm.tsx" "react-hooks/set-state-in-effect" 2 +"../../src/components/NumericInput/primitives/NumericInputContainer.tsx" "@typescript-eslint/no-unsafe-assignment" 1 +"../../src/components/NumericInput/primitives/NumericSymbolButton.tsx" "@typescript-eslint/no-unsafe-assignment" 2 "../../src/components/OfflineWithFeedback.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../src/components/OfflineWithFeedback.tsx" "import/no-cycle" 1 "../../src/components/Onfido/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 @@ -506,6 +505,10 @@ "../../src/components/SubStepForms/RegistrationNumberStep.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 "../../src/components/SwipeableView/index.native.tsx" "react-hooks/refs" 2 "../../src/components/SymbolButton.tsx" "no-restricted-syntax" 1 +"../../src/components/Table/columnResize/applyColumnWidthOverrides.ts" "@typescript-eslint/no-redundant-type-constituents" 1 +"../../src/components/Table/columnResize/applyColumnWidthOverrides.ts" "@typescript-eslint/no-unsafe-argument" 1 +"../../src/components/Table/columnResize/applyColumnWidthOverrides.ts" "@typescript-eslint/no-unsafe-assignment" 1 +"../../src/components/Table/columnResize/applyColumnWidthOverrides.ts" "@typescript-eslint/no-unsafe-member-access" 1 "../../src/components/Table/middlewares/filtering.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/Table/Table.tsx" "@typescript-eslint/no-unsafe-type-assertion" 4 "../../src/components/Table/TableBody.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 @@ -568,10 +571,6 @@ "../../src/components/VideoPlayer/index.tsx" "import/no-cycle" 2 "../../src/components/VideoPlayer/useHandleNativeVideoControls/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/VideoPlayer/VideoPlayerControls/index.tsx" "import/no-cycle" 1 -"../../src/components/VideoPlayer/VideoPlayerControls/ProgressBar/index.tsx" "@typescript-eslint/no-deprecated/Gesture.Pan" 2 -"../../src/components/VideoPlayer/VideoPlayerControls/ProgressBar/index.tsx" "@typescript-eslint/no-deprecated/PanGestureChangeEventPayload" 1 -"../../src/components/VideoPlayer/VideoPlayerControls/VolumeButton/index.tsx" "@typescript-eslint/no-deprecated/Gesture.Pan" 2 -"../../src/components/VideoPlayer/VideoPlayerControls/VolumeButton/index.tsx" "@typescript-eslint/no-deprecated/PanGestureChangeEventPayload" 1 "../../src/components/VideoPlayerContexts/PlaybackContext/playbackContextReportIDUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/VideoPlayerPreview/index.tsx" "import/no-cycle" 1 "../../src/components/VideoPopoverMenu/index.tsx" "import/no-cycle" 1 @@ -1336,7 +1335,6 @@ "../../src/pages/iou/request/step/IOURequestStepDistanceGPS/Waypoints/index.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 2 "../../src/pages/iou/request/step/IOURequestStepDistanceMap.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 3 "../../src/pages/iou/request/step/IOURequestStepDistanceMap.tsx" "react-hooks/set-state-in-effect" 1 -"../../src/pages/iou/request/step/IOURequestStepGPSTripEdit/Slider.tsx" "@typescript-eslint/no-deprecated/Gesture.Pan" 2 "../../src/pages/iou/request/step/IOURequestStepOdometerImage/index.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 "../../src/pages/iou/request/step/IOURequestStepOdometerImage/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/pages/iou/request/step/IOURequestStepScan/components/NavigationAwareCamera/WebCamera.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 From c915d1aa4e5d9c9fb0d880fd7765d6f3bf6d3f60 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Wed, 21 Oct 2026 16:12:28 +0200 Subject: [PATCH 03/37] Trim oxlint migration comments to workaround explanations --- .github/workflows/oxlint.yml | 3 +- .github/workflows/preDeploy.yml | 3 +- .gitignore | 2 +- .npmrc | 2 +- config/eslint/eslint.config.mjs | 3 +- config/oxlint/eslintDirectives.mjs | 18 +-- config/oxlint/noDeprecatedSelfClosingJsx.mjs | 9 +- config/oxlint/plugins/core-rules.mjs | 5 +- config/oxlint/plugins/expensify-rules.mjs | 3 +- config/oxlint/plugins/hosted-rules.mjs | 20 +-- .../oxlint/preferLocaleCompareFromContext.mjs | 9 +- config/oxlint/reactCompilerGate.mjs | 7 +- config/oxlint/reactCompilerRust.mjs | 46 ++---- config/oxlint/ruleNames.mjs | 9 +- config/reactCompiler/suppressedRules.mjs | 2 - oxlint.config.mts | 131 ++++++------------ scripts/lint/oxlint/OxlintLinter.ts | 36 ++--- scripts/lint/processors/Seatbelt.ts | 2 +- tests/tooling/lintPipeline.test.ts | 6 +- 19 files changed, 112 insertions(+), 204 deletions(-) diff --git a/.github/workflows/oxlint.yml b/.github/workflows/oxlint.yml index b7af5f9e0b88..ad158c3efef6 100644 --- a/.github/workflows/oxlint.yml +++ b/.github/workflows/oxlint.yml @@ -28,8 +28,7 @@ jobs: - name: Lint JavaScript and TypeScript with Oxlint id: oxlint continue-on-error: true - # oxlint runs JS plugins on one thread (oxc#26621), so OxlintLinter fans them across 4 - # single-threaded shards and runs the type-aware rules in one multi-threaded process: 8 vCPU. + # oxlint runs JS plugins on one thread (oxc#26621), so OxlintLinter splits them into 4 shards on this 8 vCPU runner. env: OXLINT_SHARDS: 4 run: npm run lint -- --linter=oxlint diff --git a/.github/workflows/preDeploy.yml b/.github/workflows/preDeploy.yml index c6f5a41a72d3..2846145b97db 100644 --- a/.github/workflows/preDeploy.yml +++ b/.github/workflows/preDeploy.yml @@ -14,8 +14,7 @@ jobs: uses: ./.github/workflows/lint.yml secrets: inherit - # Deliberately absent from confirmPassingBuild's `needs`: the Oxlint run is a shadow gate, here - # only so the post-merge job can auto-tighten its own baseline. + # Left out of confirmPassingBuild's `needs` on purpose: Oxlint is a shadow gate that runs here to auto-tighten its baseline. oxlint: uses: ./.github/workflows/oxlint.yml secrets: inherit diff --git a/.gitignore b/.gitignore index d82481e269f0..bfd9e70329b1 100644 --- a/.gitignore +++ b/.gitignore @@ -186,6 +186,6 @@ server/victory-chart-renderer/.dev/ # Bun lockfiles (VCR uses npm workspaces + package-lock.json; bun is runtime-only) bun.lock -# Oxlint leg configs derived from oxlint.config.mts for one sharded run (scripts/lint/oxlint/OxlintLinter.ts) +# Per-run Oxlint configs written by scripts/lint/oxlint/OxlintLinter.ts .oxlintrc.js-plugins.*.json .oxlintrc.type-aware.*.json diff --git a/.npmrc b/.npmrc index eb8b3f295df6..8f9e4e59c1bd 100644 --- a/.npmrc +++ b/.npmrc @@ -10,7 +10,7 @@ min-release-age-exclude[]=@expensify/react-native-wallet min-release-age-exclude[]=react-fast-pdf min-release-age-exclude[]=react-native-key-command min-release-age-exclude[]=react-native-qrcode-svg -# Omitting a binding scope makes npm silently skip the optional dependency instead of erroring. +# List each native binding scope too, or npm silently skips the optional dependency. min-release-age-exclude[]=oxlint min-release-age-exclude[]=@oxlint/* min-release-age-exclude[]=oxc-transform-react diff --git a/config/eslint/eslint.config.mjs b/config/eslint/eslint.config.mjs index 50387071594e..b145652ff4e1 100644 --- a/config/eslint/eslint.config.mjs +++ b/config/eslint/eslint.config.mjs @@ -824,8 +824,7 @@ const config = defineConfig([ globalIgnores([ '!**/.storybook', '!**/.github', - // Oxlint's own config. Its rule names are quoted object keys, which naming-convention flags by the - // hundred; `**/*.config.mjs` below is ignored for the same kind of reason. + // Oxlint's config: its quoted rule-name keys trip naming-convention hundreds of times. 'oxlint.config.mts', '.github/actions/**/index.js', '**/*.config.js', diff --git a/config/oxlint/eslintDirectives.mjs b/config/oxlint/eslintDirectives.mjs index 9cd448334fb2..a626217e8d8d 100644 --- a/config/oxlint/eslintDirectives.mjs +++ b/config/oxlint/eslintDirectives.mjs @@ -1,7 +1,5 @@ -// A rule hosted through oxlint's `jsPlugins` cannot take a native plugin's prefix (`react-hooks`, -// `@typescript-eslint`, `react`, `import` are reserved), so an existing `eslint-disable-next-line -// @typescript-eslint/naming-convention` does not suppress `hosted/naming-convention`. This makes a -// hosted rule answer to the id ESLint uses, by dropping the reports ESLint would have dropped. +// An `eslint-disable-next-line @typescript-eslint/naming-convention` does not suppress +// `hosted/naming-convention`, so this drops the reports ESLint would have suppressed under its own id. const JUSTIFICATION = /\s-{2,}\s/; const KINDS = ['eslint-disable-next-line', 'eslint-disable-line', 'eslint-disable', 'eslint-enable']; @@ -36,8 +34,8 @@ function parseDirectives(comments) { } for (const ruleId of parsed.ruleIds) { if (parsed.kind === 'eslint-disable-next-line' || parsed.kind === 'eslint-disable-line') { - // ESLint rejects only a multi-line `-line` directive; a multi-line `-next-line` is - // applied to the line after the comment closes, which is why the target is `end`. + // ESLint ignores a multi-line `-line` directive but applies a multi-line `-next-line` + // to the line after the comment ends. if (parsed.kind === 'eslint-disable-line' && start !== end) { continue; } @@ -62,9 +60,7 @@ function directivesFor(filename, comments) { return cache.get(key); } -// Mirrors ESLint: the last block directive at or before the line that names the rule (or no rule) -// decides. A bare `eslint-disable` followed by `eslint-enable foo` therefore re-enables `foo` alone, -// and a bare `eslint-enable` re-enables every rule. +// As in ESLint, the last block directive at or before the line that names the rule (or no rule) wins. function isSuppressed(directives, eslintRuleId, line) { if (line == null) { return false; @@ -77,7 +73,6 @@ function isSuppressed(directives, eslintRuleId, line) { if (block.line > line || (block.ruleId !== ALL_RULES && block.ruleId !== eslintRuleId)) { continue; } - // A report on the `eslint-enable` line itself stays suppressed, as with the previous inclusive range. if (block.disable) { disabled = true; } else if (block.line < line) { @@ -99,8 +94,7 @@ function withEslintDirectiveIds(rule, eslintRuleId) { create(context) { const filteredContext = Object.create(context, { report: { - // Forwarded as-is rather than as one descriptor: ESLint also accepts the legacy - // `report(node, message)` form, and dropping the extra arguments would change the message. + // Keeps every argument: ESLint also accepts the legacy `report(node, message)` form. value(...args) { const line = reportLine(args); if (line != null) { diff --git a/config/oxlint/noDeprecatedSelfClosingJsx.mjs b/config/oxlint/noDeprecatedSelfClosingJsx.mjs index 74723fcbb1be..7479ae40fb85 100644 --- a/config/oxlint/noDeprecatedSelfClosingJsx.mjs +++ b/config/oxlint/noDeprecatedSelfClosingJsx.mjs @@ -1,7 +1,7 @@ -// tsgolint's no-deprecated skips the signature check on self-closing JSX (``, not ``), so a -// component deprecated only through its signature, like `MenuItem` wrapped in `Object.assign`, goes -// unreported. jsPlugins get no types, so the components are listed in the options. Delete this rule once -// tsgolint reports these itself: the findings will double. +// tsgolint's no-deprecated skips the signature check on self-closing JSX (``), so a component +// deprecated only through its signature, like `MenuItem` wrapped in `Object.assign`, goes unreported. +// jsPlugins get no types, so the options list the components. Delete this rule once tsgolint reports +// these itself, or the findings will double. import fs from 'node:fs'; import path from 'node:path'; @@ -9,7 +9,6 @@ import {fileURLToPath} from 'node:url'; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..'); -// tsconfig.json allows comments and trailing commas. function readJsonc(file) { const text = fs .readFileSync(file, 'utf8') diff --git a/config/oxlint/plugins/core-rules.mjs b/config/oxlint/plugins/core-rules.mjs index 8684a2253672..6364cfe31d3b 100644 --- a/config/oxlint/plugins/core-rules.mjs +++ b/config/oxlint/plugins/core-rules.mjs @@ -1,6 +1,5 @@ -// no-invalid-this is deliberately absent, even though ESLint enables it: it reaches -// sourceCode.getJSDocComment, which oxlint's bridge answers with `throw Error("...not supported at -// present (and deprecated)")` at node_modules/oxlint/dist/lint.js:5765, erroring out 36 files. +// no-invalid-this is left out although ESLint enables it: it calls sourceCode.getJSDocComment, which +// oxlint's bridge throws on as unsupported. import {builtinRules} from 'eslint/use-at-your-own-risk'; import {withEslintDirectiveIdsFor} from '../eslintDirectives.mjs'; diff --git a/config/oxlint/plugins/expensify-rules.mjs b/config/oxlint/plugins/expensify-rules.mjs index 44972fde89f2..b8ba6a97145b 100644 --- a/config/oxlint/plugins/expensify-rules.mjs +++ b/config/oxlint/plugins/expensify-rules.mjs @@ -31,8 +31,7 @@ const plugin = { rules: { ...rules, 'no-inline-useOnyx-selector': withFullGating(rules['no-inline-useOnyx-selector']), - // ESLint's copy calls getParserServices at create() time, which throws in a jsPlugin, so a - // type-free rewrite stands in for it. + // ESLint's copy calls getParserServices in create(), which throws in a jsPlugin. 'prefer-locale-compare-from-context': {create: preferLocaleCompareCreate, meta: preferLocaleCompareMeta}, }, }; diff --git a/config/oxlint/plugins/hosted-rules.mjs b/config/oxlint/plugins/hosted-rules.mjs index 61d48962187f..3b1239b770e0 100644 --- a/config/oxlint/plugins/hosted-rules.mjs +++ b/config/oxlint/plugins/hosted-rules.mjs @@ -16,8 +16,8 @@ function rulesOf(packageName) { try { mod = require(path.resolve(repoRoot, 'node_modules', packageName)); } catch { - // A directory require ignores the package's `exports` map, so a package that ships no `main` - // (typescript-eslint) resolves only by bare specifier. + // A directory require ignores `exports`, so a package with no `main` (typescript-eslint) needs + // the bare specifier. mod = require(packageName); } return mod?.rules ?? mod?.default?.rules ?? {}; @@ -29,17 +29,17 @@ const jsdoc = rulesOf('eslint-plugin-jsdoc'); const storybook = rulesOf('eslint-plugin-storybook'); const typescriptEslint = rulesOf('@typescript-eslint/eslint-plugin'); -// eslint-plugin-react-hooks is not a root dependency: it arrives nested under eslint-config-expensify, -// and that nested copy is the one ESLint loads. Resolving by bare specifier would run a second install. +// eslint-plugin-react-hooks is not a root dependency; ESLint loads the copy nested under +// eslint-config-expensify. const reactHooks = (() => { const configEntry = require.resolve('eslint-config-expensify', {paths: [repoRoot]}); return rulesOf(require.resolve('eslint-plugin-react-hooks', {paths: [path.dirname(configEntry)]})); })(); -// naming-convention demands parser services at startup, which oxlint's JS plugins cannot provide. -// getParserServices(context, true) tolerates a missing `program` but hard-fails on missing node maps -// and reads context.languageOptions before any check, so both are stubbed. With this repo's selector -// groups the rule never reaches the type checker anyway (no `types` selector). +// naming-convention requires parser services, which oxlint's JS plugins lack. +// getParserServices(context, true) tolerates a missing `program` but fails on missing node maps and +// reads context.languageOptions first, so both are stubbed. This repo's selectors never reach the +// type checker (no `types` selector). const STUB_PARSER_SERVICES = { esTreeNodeToTSNodeMap: new Map(), tsNodeToESTreeNodeMap: new Map(), @@ -50,8 +50,8 @@ function withStubbedParserServices(rule) { ...rule, create(context) { const sourceCode = context.sourceCode ?? context.getSourceCode(); - // Prototype shadowing, not a Proxy: oxlint defines `parserServices` as a read-only, - // non-configurable data property, which a Proxy get trap may not report a different value for. + // A Proxy cannot do this: oxlint defines `parserServices` as a read-only, non-configurable + // property, and a get trap may not return a different value for it. const stubbedSourceCode = Object.create(sourceCode, { parserServices: {value: STUB_PARSER_SERVICES}, }); diff --git a/config/oxlint/preferLocaleCompareFromContext.mjs b/config/oxlint/preferLocaleCompareFromContext.mjs index c870968d854d..fe2f2f4a02df 100644 --- a/config/oxlint/preferLocaleCompareFromContext.mjs +++ b/config/oxlint/preferLocaleCompareFromContext.mjs @@ -1,7 +1,6 @@ -// oxlint's JS plugins get no type information, so the type query ESLint's -// rulesdir/prefer-locale-compare-from-context makes (`isString(getTypeAtLocation(...))`) cannot be made -// here. `localeCompare` exists on exactly one built-in prototype, so the receiver check is dropped -// rather than approximated. The resulting divergence is an object with its own `localeCompare`. +// oxlint's JS plugins get no type information, so this drops the `isString(getTypeAtLocation(...))` +// receiver check of ESLint's rulesdir/prefer-locale-compare-from-context. Only String.prototype has +// `localeCompare`; an object defining its own is the one divergence. import {createRequire} from 'node:module'; import path from 'node:path'; import {fileURLToPath} from 'node:url'; @@ -10,7 +9,7 @@ const require = createRequire(import.meta.url); const pluginDir = path.dirname(fileURLToPath(import.meta.url)); const expensifyPlugin = path.resolve(pluginDir, '../../node_modules/eslint-config-expensify/eslint-plugin-expensify'); -/** These modules are ESM; `require` hands back the namespace, so unwrap a default if there is one. */ +/** `require` of these ESM modules returns the namespace, so unwrap `default`. */ function load(relative) { const module = require(path.join(expensifyPlugin, relative)); return module?.default ?? module; diff --git a/config/oxlint/reactCompilerGate.mjs b/config/oxlint/reactCompilerGate.mjs index 38311b1b96ba..136b05a61503 100644 --- a/config/oxlint/reactCompilerGate.mjs +++ b/config/oxlint/reactCompilerGate.mjs @@ -1,5 +1,5 @@ -// oxlint has no processor concept and no way to filter a native Rust rule's diagnostics, so the -// ESLint react-compiler-compat processor is replicated here by wrapping `context.report` in a JS plugin. +// oxlint has no processors and cannot filter a native Rust rule's diagnostics, so this replicates +// ESLint's react-compiler-compat processor by wrapping `context.report` in a JS plugin. import path from 'node:path'; import {didBothCompilersMemoizeFile} from '../reactCompiler/checkBoth.mjs'; @@ -32,8 +32,7 @@ function withGating(rule, shouldGate) { create(context) { const gatedContext = Object.create(context, { report: { - // Forwarded as-is rather than as one descriptor: ESLint also accepts the legacy - // `report(node, message)` form, and dropping the extra arguments would change the message. + // Keeps every argument: ESLint also accepts the legacy `report(node, message)` form. value(...args) { if (shouldGate(rule, args[0])) { const filename = context.filename ?? context.getFilename(); diff --git a/config/oxlint/reactCompilerRust.mjs b/config/oxlint/reactCompilerRust.mjs index 8fe042c6bfa4..2728ccd4527e 100644 --- a/config/oxlint/reactCompilerRust.mjs +++ b/config/oxlint/reactCompilerRust.mjs @@ -16,12 +16,10 @@ const RULE_BY_CATEGORY = { UnsupportedSyntax: 'unsupported-syntax', }; -// A `Config` diagnostic means the options handed to the compiler are wrong, so it is never -// ignorable: a broken ENVIRONMENT would otherwise become twelve rules that silently report nothing. +// A `Config` diagnostic means the compiler options are wrong; ignoring it would silence every rule. const CONFIG_CATEGORY = 'Config'; -// ESLint does not enable the rule behind each of these, so surfacing them would be oxlint-only -// noise. +// ESLint does not enable the rules behind these categories. const IGNORED_CATEGORIES = new Set([ 'CapitalizedCalls', 'EffectDependencies', @@ -59,9 +57,8 @@ const ENVIRONMENT = { enableTreatRefLikeIdentifiersAsRefs: true, }; -// Categories are only available inside the formatted `codeframe` string, which is not a public API of -// oxc-transform-react. Hence the exact version pin in package.json and the throw on an unmapped -// category: an upstream rename has to fail loudly. +// The category is only in the formatted `codeframe` string, which is not a public API of +// oxc-transform-react. That is why package.json pins the exact version and an unmapped category throws. const CATEGORY_PATTERN = /react-compiler\(([^)]+)\)/; const cache = new Map(); @@ -91,15 +88,11 @@ function utf8Length(code) { return 4; } -// `label.start`/`label.end` are UTF-8 *byte* offsets into the source oxc parsed, while a JavaScript -// string is indexed in UTF-16 code units. Indexing `sourceText` with one directly overshoots by one -// unit for every extra byte, which drifts across newlines in any file holding non-ASCII (281 of -// src/'s .tsx files do), so a diagnostic would anchor on the wrong line and an -// `eslint-disable-next-line` on the right line would stop suppressing it. This maps a byte offset -// back to the line and UTF-16 column oxlint's `loc` expects. Built once per file rather than per -// diagnostic, hence the closure. +// `label.start`/`label.end` are UTF-8 byte offsets, while JavaScript strings index UTF-16 code units. +// Using them directly drifts in any non-ASCII file, anchoring diagnostics on the wrong line where +// `eslint-disable-next-line` no longer suppresses them. This maps a byte offset to the line and UTF-16 +// column oxlint's `loc` expects. function buildOffsetToPoint(source) { - // Byte and UTF-16 offset of the start of each line, both indexed by line - 1. const lineBytes = [0]; const lineUnits = [0]; let byte = 0; @@ -114,8 +107,6 @@ function buildOffsetToPoint(source) { } return function offsetToPoint(offset) { - // The last line starting at or before `offset`. An offset past the end of the source clamps - // to the final line rather than running off it. let low = 0; let high = lineBytes.length - 1; while (low < high) { @@ -127,7 +118,6 @@ function buildOffsetToPoint(source) { } } - // Walked rather than subtracted: a column is UTF-16 code units, not bytes. let byteInLine = lineBytes[low]; let unit = lineUnits[low]; while (byteInLine < offset && unit < source.length) { @@ -153,21 +143,18 @@ function analyze(filename, sourceText) { reactCompiler: { target: '19', outputMode: 'lint', - // Without it the binding returns only fatal diagnostics, so a non-blocking problem - // reports nothing and the rule looks clean. + // Without it the binding returns only fatal diagnostics. reportDiagnostics: true, flowSuppressions: false, - // Without it the compiler skips every function under an - // `eslint-disable-next-line react-hooks/exhaustive-deps` comment, and the repo has - // 228. Lint-only: setting it in the build configs would change what those comments - // make the build memoize, i.e. what ships. + // Without it the compiler skips every function under an `eslint-disable-next-line + // react-hooks/exhaustive-deps` comment. Lint-only: in the build configs it would change + // what ships memoized. eslintSuppressionRules: [], environment: ENVIRONMENT, }, }); } catch (error) { - // Malformed options are rejected at the binding rather than reported as a diagnostic; - // swallowing it would make one bad edit to ENVIRONMENT look like a clean repo. + // The binding throws on malformed options instead of reporting a diagnostic. const message = error instanceof Error ? error.message : String(error); if (/is none of these types|ReactCompilerOptions/.test(message)) { throw new Error(`oxc-transform-react rejected this module's React Compiler options: ${message}`); @@ -177,16 +164,13 @@ function analyze(filename, sourceText) { const errors = result.errors ?? []; - // A config oxc rejects is a bug in this module, not a finding about the file. Fail loudly, the - // same way an unmapped category does. const rejected = errors.find((error) => (error.message ?? '').startsWith('Invalid React Compiler')); if (rejected) { throw new Error(`oxc-transform-react rejected this module's React Compiler options: ${rejected.message}`); } - // A fatal result carrying no `react-compiler(...)` category means the compiler never got as far - // as analyzing: a parse failure aborts that way. oxlint's own parser reports the syntax error, so - // a rule here stays quiet rather than throwing on it. + // A fatal result with no `react-compiler(...)` category is a parse failure, which oxlint's own + // parser already reports. if (result.fatal && !errors.some((error) => CATEGORY_PATTERN.test(error.codeframe ?? ''))) { return []; } diff --git a/config/oxlint/ruleNames.mjs b/config/oxlint/ruleNames.mjs index ad72ded53b59..baaeb9801c26 100644 --- a/config/oxlint/ruleNames.mjs +++ b/config/oxlint/ruleNames.mjs @@ -1,13 +1,11 @@ -// Plain .mjs so the bun lint pipeline and oxlint's JS plugin runtime can both read one copy; only the -// first of those can load TypeScript. +// Plain `.mjs`: oxlint's JS plugin runtime cannot load TypeScript. const OXLINT_RULE_RENAMES = { 'no-object-constructor': 'no-new-object', 'no-new-native-nonconstructor': 'no-new-symbol', }; -// A rule hosted through oxlint's `jsPlugins` cannot take a native plugin's prefix (`react`, -// `import`, `react-hooks` and `@typescript-eslint` are reserved), so it is registered bare under -// `hosted` and this table is what gives it back its ESLint identity. +// oxlint reserves native plugin prefixes (`react`, `import`, `react-hooks`, `@typescript-eslint`), so +// hosted rules register under `hosted` and this maps each back to its ESLint plugin. const HOSTED_RULE_ORIGIN = { 'jsx-no-bind': 'react', 'function-component-definition': 'react', @@ -51,7 +49,6 @@ const HOSTED_RULE_ORIGIN = { 'use-storybook-testing-library': 'storybook', }; -// Repo-written hosted rules that report under an existing ESLint rule id. const HOSTED_RULE_ESLINT_ID = { 'no-deprecated-self-closing-jsx': '@typescript-eslint/no-deprecated', }; diff --git a/config/reactCompiler/suppressedRules.mjs b/config/reactCompiler/suppressedRules.mjs index 6c1664e644d5..46d77c325c64 100644 --- a/config/reactCompiler/suppressedRules.mjs +++ b/config/reactCompiler/suppressedRules.mjs @@ -1,8 +1,6 @@ // Plain `.mjs`: oxlint's JS plugin runtime cannot load TypeScript. const RULES_SUPPRESSED_BY_REACT_COMPILER = new Set(['react/jsx-no-constructed-context-values', 'rulesdir/no-inline-useOnyx-selector']); -// Only the "wrap in useCallback/useMemo" suggestions are false positives in compiled files; -// warnings about genuinely missing dependencies are not suppressed. const EXHAUSTIVE_DEPS_USECALLBACK_USEMEMO_PATTERN = /\buseCallback\(\) Hook\b|\buseMemo\(\) Hook\b/; export {EXHAUSTIVE_DEPS_USECALLBACK_USEMEMO_PATTERN, RULES_SUPPRESSED_BY_REACT_COMPILER}; diff --git a/oxlint.config.mts b/oxlint.config.mts index b1be3774326b..134113602ff0 100644 --- a/oxlint.config.mts +++ b/oxlint.config.mts @@ -1,8 +1,7 @@ import {defineConfig} from 'oxlint'; -// Every block that sets `no-restricted-imports` has to restate the whole option object, because an -// oxlint override replaces it rather than merging into it. Building each variant from these keeps the -// copies identical, the way `restrictedImportPaths` does in config/eslint/eslint.config.mjs. +// An oxlint override replaces a rule's options instead of merging them, so every block that sets +// `no-restricted-imports` restates the full list, built from these. const restrictedImportPaths = [ { name: 'react-native', @@ -30,10 +29,8 @@ const restrictedImportPaths = [ message: "Please use 'PressableWithFeedback' and/or 'PressableWithoutFeedback' from '@components/Pressable' instead.", }, { - // ESLint carries this under @typescript-eslint/no-restricted-imports, which oxlint does - // not implement; its core rule accepts allowTypeImports, so the path lands in every - // block that re-sets no-restricted-imports, because an oxlint override replaces the - // option object rather than merging into it. + // ESLint bans this through @typescript-eslint/no-restricted-imports, which oxlint lacks; its + // core rule accepts allowTypeImports instead. name: 'react-native-onyx/dist/OnyxUtils', message: 'OnyxUtils is not a sanctioned way to read Onyx data. Use useOnyx() from @hooks/useOnyx in render paths, or a short-lived Onyx.connectWithoutView() for non-render logic. Type-only imports are still allowed.', @@ -73,7 +70,6 @@ const restrictedImportPatterns = [ {group: ['@styles/theme/illustrations/themes/**'], message: 'Do not import theme illustrations directly. Please use the `useThemeIllustrations` hook instead.'}, ]; -// TypeScript files also ban three helpers that have safer replacements. const typescriptRestrictedImportPatterns = [ ...restrictedImportPatterns, { @@ -95,10 +91,8 @@ const typescriptRestrictedImportPatterns = [ }, ]; -// `HeaderWithBackButton` is being migrated to the composed `Header` (`@components/Header`). -// Two patterns for what ESLint writes as one: ESLint matches with gitignore semantics, where a bare -// directory name also covers everything under it, and oxlint matches the path exactly. The second -// entry is what catches `.../HeaderWithBackButton/types`. +// Two patterns where ESLint has one: ESLint's gitignore semantics let a directory name cover its +// contents, but oxlint matches the path exactly, so the second catches `.../HeaderWithBackButton/types`. const restrictedHeaderImportPatterns = [ {group: ['**/HeaderWithBackButton'], message: 'HeaderWithBackButton is being migrated to the composed Header. Please use `@components/Header` instead for new usages.'}, {group: ['**/HeaderWithBackButton/**'], message: 'HeaderWithBackButton is being migrated to the composed Header. Please use `@components/Header` instead for new usages.'}, @@ -110,9 +104,7 @@ const victoryChartRendererRestrictedImportPatterns = typescriptRestrictedImportP export default defineConfig({ plugins: ['typescript', 'import', 'jsx-a11y', 'unicorn', 'react'], - // Aliases are declared here because oxlint reserves `react`, `import` and `jsdoc` for its native - // ports, and a jsPlugin rule id always carries a prefix. - // prefix ESLint uses, so declaring it would only add noise. + // oxlint reserves `react`, `import` and `jsdoc` for its native ports, so jsPlugins use these aliases. jsPlugins: [ {name: 'rulesdir', specifier: './config/oxlint/plugins/expensify-rules.mjs'}, {name: 'report-name-utils', specifier: './config/eslint/plugins/eslint-plugin-report-name-utils.mjs'}, @@ -120,7 +112,7 @@ export default defineConfig({ {name: 'hosted', specifier: './config/oxlint/plugins/hosted-rules.mjs'}, {name: 'rc', specifier: './config/oxlint/plugins/rc-rules.mjs'}, ], - // Categories off: every rule is listed explicitly to mirror eslint.config.mjs. + // Every rule is listed explicitly to mirror eslint.config.mjs. categories: {correctness: 'off'}, options: {typeAware: true}, env: {builtin: true, es2026: true, node: true, browser: true, audioworklet: true, jest: true}, @@ -129,8 +121,6 @@ export default defineConfig({ react: {version: '19.2.3'}, propWrapperFunctions: ['forbidExtraProps', 'exact', 'Object.freeze'], progress: {hide: false}, - // `eslint-seatbelt/configure` is off: eslint-seatbelt suppresses through an ESLint processor, - // which oxlint has no concept of. }, ignorePatterns: [ 'oxlint.config.mts', @@ -314,8 +304,7 @@ export default defineConfig({ 'import/no-duplicates': 'error', 'import/extensions': ['error', 'ignorePackages', {js: 'never', mjs: 'never', jsx: 'never', ts: 'never', tsx: 'never', cts: 'never', mts: 'never'}], 'import/newline-after-import': 'error', - // OFF, shadowed not dropped: oxlint's native port anchors its reports on a different line - // than ESLint's, so ESLint's own rule runs instead as `hosted/prefer-default-export`. + // The native port anchors on a different line than ESLint's; `hosted/prefer-default-export` runs instead. 'import/prefer-default-export': 'off', 'import/no-absolute-path': 'error', 'import/no-webpack-loader-syntax': 'error', @@ -356,16 +345,13 @@ export default defineConfig({ 'rulesdir/require-live-region-for-status-updates': 'error', 'rulesdir/require-locale-for-localized-date-format': 'error', 'rulesdir/use-double-negation-instead-of-boolean': 'error', - // OFF, blocked upstream: needs the type of the `&&` left operand; jsPlugins get no type info. + // Needs the type of the `&&` left operand, and jsPlugins get no type info. 'rulesdir/boolean-conditional-rendering': 'off', - // OFF, superseded: needs typeChecker.isArrayType, and jsPlugins get no type info. - // `unicorn/prefer-at` at default options stands in for the x[x.length - N] family. + // Needs typeChecker.isArrayType; `unicorn/prefer-at` stands in. 'rulesdir/prefer-at': 'off', - // OFF here, ON for src/**/*.ts(x): ESLint's copy needs the type checker, so oxlint runs a - // type-free rewrite (config/oxlint/preferLocaleCompareFromContext.mjs) instead. + // ESLint's copy needs the type checker; a type-free rewrite runs for src/**/*.ts(x) below. 'rulesdir/prefer-locale-compare-from-context': 'off', - // Hosted behind the React Compiler gate (config/oxlint/reactCompilerGate.mjs): ESLint's - // processor drops this rule's messages in a dual-memoized file, and the gate mirrors that. + // Behind config/oxlint/reactCompilerGate.mjs, like ESLint's react-compiler-compat processor. 'rulesdir/no-inline-useOnyx-selector': 'error', 'no-array-constructor': 'error', 'no-bitwise': 'error', @@ -382,8 +368,7 @@ export default defineConfig({ 'unicorn/prefer-array-find': 'error', 'unicorn/prefer-set-size': 'error', 'unicorn/prefer-string-replace-all': 'error', - // ON with no ESLint counterpart: stands in for `rulesdir/prefer-at`, which needs the type - // checker. Default options only; `checkAllIndexAccess` is what makes it unusable. + // Stands in for `rulesdir/prefer-at`. Default options only: `checkAllIndexAccess` makes it unusable. 'unicorn/prefer-at': 'error', 'no-delete-var': 'error', 'no-label-var': 'error', @@ -467,7 +452,6 @@ export default defineConfig({ 'react/prefer-es6-class': ['error', 'always'], 'react/self-closing-comp': 'error', 'react/jsx-no-target-blank': ['error', {enforceDynamicLinks: 'always'}], - // Dead on both tools: after the JS override below only .ts/.mts/.cts stay in scope, and JSX does not parse there. 'react/jsx-filename-extension': 'off', 'react/jsx-no-comment-textnodes': 'error', 'react/no-render-return-value': 'error', @@ -482,12 +466,10 @@ export default defineConfig({ 'react/state-in-constructor': ['error', 'always'], 'react/jsx-no-script-url': ['error', [{name: 'Link', props: ['to']}]], 'react/jsx-no-useless-fragment': 'error', - // OFF, shadowed not dropped: oxlint's native port also fires inside Jest mock factories, - // where ESLint's does not. ESLint's own rule runs as `hosted/function-component-definition`. + // The native port also fires inside Jest mock factories; `hosted/function-component-definition` runs instead. 'react/function-component-definition': 'off', - // OFF, ESLint's own rule runs as `hosted/jsx-no-constructed-context-values` behind the React - // Compiler gate, which a native Rust rule cannot have. The native port diverges too: it - // anchors on the JSX provider rather than the declaration. + // A native Rust rule cannot sit behind the React Compiler gate, and this port anchors on the JSX + // provider instead of the declaration; `hosted/jsx-no-constructed-context-values` runs instead. 'react/jsx-no-constructed-context-values': 'off', 'react/no-unstable-nested-components': 'error', 'react/no-namespace': 'error', @@ -581,8 +563,7 @@ export default defineConfig({ {selector: 'LabeledStatement', message: 'Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand.'}, {selector: 'WithStatement', message: '`with` is disallowed in strict mode because it makes code impossible to predict and optimize. It is also deprecated.'}, ], - // NOT oxlint's native react/exhaustive-deps: it is a Rust rule, and the React Compiler - // message gate can only wrap a JS plugin. See config/oxlint/plugins/hosted-rules.mjs. + // Hosted because the React Compiler gate can only wrap a JS plugin, and the native port is Rust. 'hosted/exhaustive-deps': 'error', 'rc/refs': 'error', 'rc/set-state-in-effect': 'error', @@ -606,15 +587,13 @@ export default defineConfig({ 'hosted/prefer-default-export': 'error', 'hosted/order': ['error', {groups: [['builtin', 'external', 'internal']], distinctGroup: true, sortTypesGroup: false, named: false, warnOnUnassignedImports: false}], 'hosted/no-types': 'error', - // no-invalid-this is off: ESLint's implementation reaches sourceCode.getJSDocComment, which - // oxlint's JS bridge answers with a throw. 'core/no-octal': 'error', 'core/no-octal-escape': 'error', 'core/no-undef-init': 'error', 'core/strict': ['error', 'never'], 'one-var': ['error', 'never'], - // Stands in for ESLint's deprecated `no-new-object`: oxlint only implements the new name, - // which also flags bare `Object()` while not flagging `new Object(arg)`. + // oxlint only has the new name of ESLint's deprecated `no-new-object`. It also flags bare + // `Object()` but not `new Object(arg)`. 'no-object-constructor': 'error', 'hosted/no-import-module-exports': ['error', {exceptions: []}], 'hosted/no-relative-packages': 'error', @@ -696,10 +675,9 @@ export default defineConfig({ 'jsdoc/check-tag-names': 'error', 'import/named': 'error', 'no-undef': ['error', {typeof: false}], - // Stands in for ESLint's `no-new-symbol`, deprecated in 9.x for this rule. Same - // rename mechanism as no-new-object -> no-object-constructor, see OXLINT_RENAMES. + // oxlint's name for ESLint's deprecated `no-new-symbol`; see OXLINT_RULE_RENAMES. 'no-new-native-nonconstructor': 'error', - // No native oxlint rule of any name; hosted from ESLint core through `core/`. + // oxlint has no native dot-notation. 'core/dot-notation': ['error', {allowKeywords: true, allowPattern: ''}], 'core/no-dupe-args': 'error', 'core/no-return-await': 'error', @@ -709,7 +687,6 @@ export default defineConfig({ { files: ['**/*.{ts,tsx,cts,mts}'], rules: { - // Core rules the TypeScript compiler enforces; off as typescript-eslint's eslint-recommended does for TS. 'no-class-assign': 'off', 'no-new-native-nonconstructor': 'off', 'no-with': 'off', @@ -727,8 +704,8 @@ export default defineConfig({ 'typescript/no-non-null-asserted-optional-chain': 'error', 'typescript/no-redundant-type-constituents': 'error', 'typescript/no-this-alias': 'error', - // ON although ESLint reports 0: tsgolint's TS7 inference disagrees with the repo's - // TS 6.0.2 about which assertions are unnecessary. + // ESLint reports 0 here: tsgolint's TS7 inference disagrees with the repo's TS 6.0.2 + // about which assertions are unnecessary. 'typescript/no-unnecessary-type-assertion': 'error', 'typescript/no-unnecessary-type-constraint': 'error', 'typescript/no-unsafe-declaration-merging': 'error', @@ -742,8 +719,7 @@ export default defineConfig({ 'typescript/restrict-template-expressions': 'error', 'typescript/triple-slash-reference': 'error', 'typescript/unbound-method': 'error', - // Hosted because tsgolint has not implemented it; oxlint rejects `@typescript-eslint` - // as a JS-plugin name, so it reports under the `hosted/` alias. + // Hosted because tsgolint has not implemented it. 'hosted/naming-convention': [ 'error', {selector: ['variable', 'property'], format: null, filter: {regex: '^__esModule$', match: true}}, @@ -753,8 +729,6 @@ export default defineConfig({ {selector: ['parameter', 'method'], format: ['camelCase', 'PascalCase'], leadingUnderscore: 'allow'}, ], 'core/lines-between-class-members': ['error', 'always', {exceptAfterSingleLine: false}], - // Every "off" in this block mirrors ESLint's resolved config for TS files: TypeScript-covered core rules, - // and typed rules eslint-config-expensify leaves off. Parity, not coverage gaps. 'typescript/adjacent-overload-signatures': 'error', 'typescript/array-type': ['error', {default: 'array-simple'}], 'typescript/ban-tslint-comment': 'error', @@ -801,8 +775,8 @@ export default defineConfig({ 'typescript/no-non-null-assertion': 'error', 'typescript/prefer-enum-initializers': 'error', 'typescript/no-import-type-side-effects': 'error', - // Accepted gap: oxlint compares constituents syntactically, so `{a: string} & {a: string}` - // passes where typescript-eslint's type-checker comparison reports it. + // oxlint compares constituents syntactically, so `{a: string} & {a: string}` passes where + // typescript-eslint reports it. 'typescript/no-duplicate-type-constituents': ['error', {ignoreUnions: true}], }, plugins: ['jsdoc'], @@ -810,7 +784,6 @@ export default defineConfig({ { files: ['**/scripts/**', '**/server/**', '**/.github/**'], rules: { - // CLI output; ESLint resolves no-console off for these directories too. 'no-console': 'off', }, }, @@ -831,7 +804,6 @@ export default defineConfig({ 'jestSetup.js', ], rules: { - // Mirrors ESLint's test-file block. 'no-console': 'off', 'no-await-in-loop': 'off', 'no-import-assign': 'off', @@ -842,7 +814,6 @@ export default defineConfig({ { files: ['**/*.{ts,tsx,js,jsx,mjs,cjs}'], rules: { - // Every "off" in this block is off in eslint.config.mjs for the same files. 'react-native-a11y/has-accessibility-props': 'error', 'react-native-a11y/has-valid-accessibility-actions': 'error', 'react-native-a11y/has-valid-accessibility-component-type': 'error', @@ -910,7 +881,6 @@ export default defineConfig({ { files: ['**/*.{js,jsx,mjs,cjs}'], rules: { - // Mirrors ESLint's plain-JS block. 'prefer-arrow-callback': 'off', 'lodash/import-scope': 'off', }, @@ -919,7 +889,6 @@ export default defineConfig({ { files: ['**/*.{mjs,cjs}', '.storybook/**/*.{ts,tsx}', 'config/rsbuild/**/*.ts'], rules: { - // Node ESM, Storybook and the Rsbuild config need explicit extensions on relative imports; off in ESLint too. 'import/extensions': 'off', }, }, @@ -988,11 +957,10 @@ export default defineConfig({ 'you-dont-need-lodash-underscore/replace': 'error', 'you-dont-need-lodash-underscore/omit': 'error', 'you-dont-need-lodash-underscore/flatten': 'error', - // throttle and clone-deep stay off as in ESLint; structuredClone, the suggested clone-deep replacement, is - // unsupported in Hermes (facebook/hermes#68). 'you-dont-need-lodash-underscore/throttle': 'off', 'you-dont-need-lodash-underscore/is-string': 'error', 'you-dont-need-lodash-underscore/cast-array': 'error', + // structuredClone, the suggested clone-deep replacement, is not supported in Hermes: https://github.com/facebook/hermes/issues/684 'you-dont-need-lodash-underscore/clone-deep': 'off', 'you-dont-need-lodash-underscore/is-function': 'error', 'you-dont-need-lodash-underscore/union-by': 'error', @@ -1007,7 +975,6 @@ export default defineConfig({ { files: ['.github/**/*', 'scripts/**/*', 'server/**/*', 'tests/**/*'], rules: { - // Sequential awaits are fine in scripts and tests; off in eslint.config.mjs for the same directories. 'no-await-in-loop': 'off', 'core/no-restricted-syntax': ['error', 'ForInStatement', 'LabeledStatement', 'WithStatement'], }, @@ -1019,8 +986,7 @@ export default defineConfig({ }, }, { - // An oxlint override replaces the rule value rather than merging, so the airbnb browser - // list from the root is deliberately not repeated here. + // This replaces the root's list, so the airbnb browser globals are left out on purpose. files: ['.github/actions/**/*.ts', '.github/libs/**/*.ts'], rules: { 'no-restricted-globals': [ @@ -1034,7 +1000,6 @@ export default defineConfig({ { files: ['tests/**/*'], rules: { - // Mirrors ESLint's tests block. 'import/extensions': 'off', 'no-import-assign': 'off', '@dword-design/import-alias/prefer-alias': ['off'], @@ -1053,10 +1018,8 @@ export default defineConfig({ files: ['**/*.{ts,tsx}'], rules: { 'no-restricted-imports': ['error', {paths: restrictedImportPaths, patterns: [...typescriptRestrictedImportPatterns, ...restrictedHeaderImportPatterns]}], - // Overrides are last-wins, so this block's position is load-bearing twice: this rule - // has to stay above the 83-file block near the end of `overrides` that switches it - // off, and `no-restricted-imports` above has to stay above the two blocks that re-set - // it for 24 files between them. + // Overrides are last-wins: keep this block above the later ones that turn this rule off + // or re-set `no-restricted-imports`. 'typescript/no-deprecated': ['error', {allow: ['translateFn']}], // tsgolint misses ``; see config/oxlint/noDeprecatedSelfClosingJsx.mjs. 'hosted/no-deprecated-self-closing-jsx': [ @@ -1077,7 +1040,6 @@ export default defineConfig({ { files: ['modules/ExpensifyNitroUtils/src/**/*'], rules: { - // Off in eslint.config.mjs for the same files. 'typescript/consistent-type-definitions': 'off', }, }, @@ -1096,7 +1058,7 @@ export default defineConfig({ {files: ['src/styles/**'], excludeFiles: ['src/styles/typography.ts', 'src/styles/variables.ts'], rules: {'rulesdir/no-raw-typography': ['error', {allowVariablesReferences: true}]}}, {files: ['**/en.ts', '**/es.ts'], rules: {'rulesdir/use-periods-for-error-messages': 'error'}}, {files: ['src/libs/ReportNameUtils.ts'], rules: {'report-name-utils/no-function-call-in-get-report-name': 'error'}}, - // reportAttributes.ts computes report names, so it keeps the base list without the TypeScript-only bans. + // reportAttributes.ts computes report names, so the TypeScript-only bans do not apply. { files: ['src/libs/actions/OnyxDerived/configs/reportAttributes.ts'], rules: {'no-restricted-imports': ['error', {paths: restrictedImportPaths, patterns: restrictedImportPatterns}]}, @@ -1104,15 +1066,14 @@ export default defineConfig({ { files: ['src/languages/**', 'src/CONST/index.ts', 'src/NAICS.ts'], rules: { - // ESLint applies max-lines to src/** with these files in its ignores list. 'max-lines': 'off', }, }, { files: ['scripts/**', '.github/**'], rules: { - // Deliberate difference, not a mirror: tsgolint's no-unsafe-* port is stricter than typescript-eslint's and this - // loosely typed Actions glue is where it shows. Removing this block adds 695 findings ESLint reports 0 of. + // tsgolint's no-unsafe-* port is stricter than typescript-eslint's, and this loosely typed + // Actions glue is where it shows: hundreds of findings ESLint does not report. 'typescript/no-unsafe-assignment': 'off', 'typescript/no-unsafe-argument': 'off', 'typescript/no-unsafe-return': 'off', @@ -1221,8 +1182,8 @@ export default defineConfig({ 'tests/utils/createSharedValueMock.ts', ], rules: { - // tsgolint reports writes to deprecated properties, typescript-eslint does not (typescript-eslint#10643); off - // where that would flood. Costs 5 read-site findings ESLint reports in these files. + // tsgolint also reports writes to deprecated properties (typescript-eslint#10643), which + // floods these files. Costs a few read-site findings ESLint reports here. 'typescript/no-deprecated': 'off', 'hosted/no-deprecated-self-closing-jsx': 'off', }, @@ -1230,7 +1191,6 @@ export default defineConfig({ { files: ['**/*.{js,jsx,mjs,cjs}'], rules: { - // Typed rules off for plain JS, as typescript-eslint does; parity, not coverage gaps. 'typescript/await-thenable': 'off', 'typescript/consistent-type-exports': 'off', 'typescript/dot-notation': 'off', @@ -1267,26 +1227,22 @@ export default defineConfig({ files: ['**/*.d.{ts,tsx}'], rules: { // ESLint's code-path analyzer throws on declaration files under oxlint's plugin bridge - // (`Cannot read properties of undefined (reading 'parent')`), and a thrown plugin fails the run. + // (`Cannot read properties of undefined (reading 'parent')`), failing the run. 'core/no-unreachable-loop': 'off', }, }, { - // Mirrors config/eslint/eslint.config.mjs: bun-types declares - // `expect(...).resolves` / `.rejects` as returning `void` even though Bun's own docs - // recommend (and its runtime requires) awaiting them, so the rule reports every correct - // use of that pattern here. See https://github.com/oven-sh/bun/pull/23425. Without this - // block oxlint reports 30 findings across 7 files that ESLint never shows. + // bun-types declares `expect(...).resolves`/`.rejects` as returning `void` even though Bun + // requires awaiting them. See https://github.com/oven-sh/bun/pull/23425. files: ['tests/tooling/**/*.ts'], rules: {'typescript/await-thenable': 'off'}, }, { - // oxlint has no storybook plugin, so the story-file scope `plugin:storybook/recommended` - // applies internally is restated here, with the upstream severities. + // oxlint has no storybook plugin, so this restates the story-file scope and severities of + // `plugin:storybook/recommended`. files: ['**/*.{stories,story}.{ts,tsx,js,jsx,mjs,cjs}'], rules: { - // The storybook preset turns this rule off for story globs; oxlint enabled it - // everywhere, so this mirrors the preset's scope. + // The storybook preset turns this off for stories. 'react/rules-of-hooks': 'off', 'hosted/await-interactions': 'error', 'hosted/context-in-play-function': 'error', @@ -1301,14 +1257,11 @@ export default defineConfig({ }, }, { - // The one storybook rule upstream scopes to the config file rather than to stories: it - // reads the addon list off disk and checks each one is installed. files: ['.storybook/main.{js,cjs,mjs,ts}'], rules: {'hosted/no-uninstalled-addons': 'error'}, }, {files: ['src/types/onyx/**/*.ts'], rules: {'hosted/require-jsdoc': ['error', {contexts: ['TSInterfaceDeclaration', 'TSTypeAliasDeclaration']}]}}, { - // Mirrors eslint.config.mjs: the personal-details wrappers, the key declaration and test setup are exempt. files: [ 'src/ONYXKEYS.ts', 'src/hooks/usePersonalDetails.ts', diff --git a/scripts/lint/oxlint/OxlintLinter.ts b/scripts/lint/oxlint/OxlintLinter.ts index 5af5c65ac3a4..eba67b27239d 100644 --- a/scripts/lint/oxlint/OxlintLinter.ts +++ b/scripts/lint/oxlint/OxlintLinter.ts @@ -12,8 +12,8 @@ import {LINT_SEVERITY} from '../types'; const OXLINT_FILE_COUNT_KEY = 'number_of_files' as const; const OXLINT_CONFIG_FILE = 'oxlint.config.mts'; -// Not `npx`: it passes the command to `sh -c` as one string, and Linux caps a single argv element at -// 128 KB, so a shard's file list fails to exec with E2BIG (exit 249). macOS has no per-element cap. +// `npx` passes the command to `sh -c` as one string, and Linux caps a single argv element at 128 KB, +// so a shard's file list would fail with E2BIG (exit 249). const OXLINT_BIN = path.join('node_modules', '.bin', 'oxlint'); type OxlintSpan = { @@ -79,14 +79,14 @@ const SIGNAL_EXIT_CODE = 128; const OXLINT_WARNING_SEVERITIES = new Set(['warning', 'advice']); const GIB = 1073741824; -// Workaround for oxlint running every JS plugin on one thread (oxc#26621): JS-plugin rules are fanned -// across `--threads=1` processes with type information off, and type-aware rules run once in a separate -// process, because each oxlint process spawns its own multi-GB `oxlint-tsgolint` type program. +// oxlint runs every JS plugin on one thread (oxc#26621), so JS-plugin rules are split across +// `--threads=1` processes without type information. Type-aware rules run in one separate process, +// because each oxlint process spawns its own multi-GB `oxlint-tsgolint` type program. const JS_SHARD_MEM_GB = 2; const TYPE_AWARE_MEM_GB = 10; -// On macOS `os.freemem()` counts only free pages and ignores the inactive, speculative and purgeable -// pages the kernel reclaims on demand, so it reads a few GB on an idle machine with tens of GB available. +// On macOS `os.freemem()` ignores inactive, speculative and purgeable pages the kernel reclaims on +// demand, so it reads a few GB on an idle machine with tens of GB available. function availableMemoryBytes(): number { if (process.platform === 'darwin') { const vmStat = Bun.spawnSync(['vm_stat']); @@ -203,12 +203,11 @@ function normalizeSeverity(severity: string | undefined): LintSeverity { return OXLINT_WARNING_SEVERITIES.has(severity ?? '') ? LINT_SEVERITY.WARNING : LINT_SEVERITY.ERROR; } -/** Oxlint splits a finding across `message`, `help` and `note`; ESLint has only `message`. */ function joinDiagnosticText(diagnostic: OxlintDiagnostic): string { return [diagnostic.message, diagnostic.help, diagnostic.note].filter(Boolean).join('\n'); } -/** Oxlint reports cwd-relative paths. They must be absolute or every seatbelt row silently misses. */ +/** Oxlint reports cwd-relative paths, which every seatbelt row would silently miss. */ function toAbsolute(projectRoot: string, filename: string): string { return path.isAbsolute(filename) ? filename : path.resolve(projectRoot, filename); } @@ -225,10 +224,7 @@ function toLintMessage(projectRoot: string, diagnostic: OxlintDiagnostic, ruleID }; } -/** - * `lintedFiles` comes from a separate `--debug=files` pass because oxlint's JSON omits every file - * that produced no diagnostic. - */ +/** `lintedFiles` comes from a separate `--debug=files` pass because oxlint's JSON omits clean files. */ function normalizeOxlintDiagnostics(diagnostics: OxlintDiagnostic[], projectRoot: string, lintedFiles: readonly string[]): LintFileResult[] { const byFile = new Map(); for (const filePath of lintedFiles) { @@ -277,7 +273,6 @@ function isOxlintConfig(value: unknown): value is OxlintConfig { return typeof value === 'object' && value !== null && !Array.isArray(value); } -/** The config is a TypeScript module, so it is imported rather than parsed; bun runs `.mts` natively. */ async function loadOxlintConfig(projectRoot: string): Promise { const loaded: unknown = await import(path.join(projectRoot, OXLINT_CONFIG_FILE)); const config = typeof loaded === 'object' && loaded !== null && 'default' in loaded ? loaded.default : undefined; @@ -292,9 +287,9 @@ function isOxlintReport(value: unknown): value is OxlintJSONReport { } /** - * Oxlint exits 1 for findings, for no files matched, and for a config that failed to parse. Only - * the first is a normal run, and `LintPipeline` treats everything at or below 1 as one, so the - * other two are promoted here. A config error is recognizable by producing no JSON at all. + * Oxlint exits 1 for findings, for no matched files, and for a config that failed to parse. + * `LintPipeline` treats exit 1 as a normal run, so the other two are promoted here. A config error + * produces no JSON. */ function parseOxlintStdout(stdout: string, stderr: string, exitCode: number, projectRoot: string, lintedFiles: readonly string[]): LinterResult { const jsonText = extractJSONObject(stdout); @@ -330,10 +325,9 @@ function parseOxlintStdout(stdout: string, stderr: string, exitCode: number, pro } /** - * `--type-aware` is not passed: `oxlint.config.mts` already sets `options.typeAware`. - * `--no-error-on-unmatched-pattern` is not passed either, because it turns a mistyped path into a - * silent clean pass; the empty-file-list case is detected explicitly instead. - * Oxlint has no `--cache`, so the pipeline's `--no-cache` flag has no effect here. + * `--no-error-on-unmatched-pattern` is left off because it turns a mistyped path into a silent clean + * pass; the empty-file-list case is detected explicitly instead. Oxlint has no `--cache`, so + * `--no-cache` has no effect here. */ class OxlintLinter extends Linter { readonly name = 'oxlint'; diff --git a/scripts/lint/processors/Seatbelt.ts b/scripts/lint/processors/Seatbelt.ts index d521d8d1f4df..59e6136ea6e7 100644 --- a/scripts/lint/processors/Seatbelt.ts +++ b/scripts/lint/processors/Seatbelt.ts @@ -481,7 +481,7 @@ async function applySeatbelt(messages: LintMessage[], options: SeatbeltOptions, const shouldWrite = anyChanged && !options.frozen && !options.readOnly; if (pruned > 0) { const verb = shouldWrite ? 'removed' : 'would remove'; - // stderr, not stdout: --format=json writes the report to stdout and this would corrupt it. + // stdout carries the --format=json report. console.error(`eslint-seatbelt: ${verb} ${pruned} baseline row(s) for deleted files`); } if (shouldWrite) { diff --git a/tests/tooling/lintPipeline.test.ts b/tests/tooling/lintPipeline.test.ts index c47c99a1eabe..01ee5b8917e2 100644 --- a/tests/tooling/lintPipeline.test.ts +++ b/tests/tooling/lintPipeline.test.ts @@ -542,16 +542,13 @@ describe('oxlint sharding', () => { }); it('only a shard that produced no JSON is worth retrying', () => { - // A killed process writes nothing, and a config error prints its complaint without JSON. expect(producedNoJSON('')).toBe(true); expect(producedNoJSON('Failed to parse oxlint configuration file.\n x Rule not found\n')).toBe(true); - // Deterministic failures are not retried: both of these carry a JSON body. expect(producedNoJSON(oxlintStdout([oxlintDiagnostic({code: undefined, message: 'plugin blew up'})]))).toBe(false); expect(producedNoJSON(oxlintStdout([oxlintDiagnostic()], 1))).toBe(false); expect(producedNoJSON(oxlintStdout([]))).toBe(false); - // Oxlint prints warnings ahead of the JSON, which must not read as a dead shard. expect(producedNoJSON(`No files found to lint.\n${oxlintStdout([])}`)).toBe(false); }); }); @@ -594,8 +591,7 @@ function knownOxlintPlugins(schema: unknown): string[] { describe('oxlint config', () => { it('names only plugins oxlint knows, at the root and in every override', async () => { - // An unknown name in an override's `plugins` array is not an error: the override's other rules - // keep working while the rules the missing plugin owns report nothing. + // oxlint accepts an unknown plugin name in an override and silently runs none of its rules. const root = path.join(import.meta.dir, '..', '..'); const known = new Set(knownOxlintPlugins(JSON.parse(fs.readFileSync(path.join(root, 'node_modules/oxlint/configuration_schema.json'), 'utf8')))); expect(known.size).toBeGreaterThan(5); From f9533a9b232bf18f24e926be06aba4944b0bd580 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Wed, 21 Oct 2026 16:13:05 +0200 Subject: [PATCH 04/37] Skip the oxlint React Compiler gate inside the lint pipeline and drop lint-oxlint --- config/oxlint/reactCompilerGate.mjs | 6 ++++++ package.json | 1 - 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/config/oxlint/reactCompilerGate.mjs b/config/oxlint/reactCompilerGate.mjs index 136b05a61503..e8df84b26b11 100644 --- a/config/oxlint/reactCompilerGate.mjs +++ b/config/oxlint/reactCompilerGate.mjs @@ -26,7 +26,13 @@ function resolveMessage(rule, descriptor) { return template.replaceAll(/\{\{\s*(\w+)\s*\}\}/g, (match, key) => descriptor.data?.[key] ?? match); } +// Only runs outside scripts/lint need this (the editor, raw `oxlint`): without it they flag these rules +// in files the React Compiler memoizes, and the fix they suggest (useCallback/useMemo) is banned there. function withGating(rule, shouldGate) { + // scripts/lint already drops these messages in ReactCompilerFilter, so skip a second compile. + if (process.env.LINT_PIPELINE === '1') { + return rule; + } return { ...rule, create(context) { diff --git a/package.json b/package.json index aa24ddbfb0b4..0e8bad9c38ce 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,6 @@ "perf-local": "NODE_OPTIONS=--experimental-vm-modules npx reassure --testMatch '/tests/perf-local/**/*.perf-local.ts'", "typecheck": "bun scripts/typecheck.ts", "lint": "bun scripts/lint/index.ts", - "lint-oxlint": "oxlint .", "lint-changed": "./scripts/lintChanged.sh", "lint-watch": "onchange '**/*.{js,jsx,ts,tsx,mjs,cjs}' -- bun scripts/lint/index.ts {{changed}}", "eslint-report": "bun scripts/eslint-report.ts", From 26c2b62eaac8df4777ace982d4f7f6bea68e5bbc Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Wed, 21 Oct 2026 17:23:04 +0200 Subject: [PATCH 05/37] Use native no-unreachable-loop, drop dead oxlint mappings, clarify workaround comments --- config/oxlint/plugins/core-rules.mjs | 19 +++++------------ config/oxlint/ruleNames.mjs | 4 +--- oxlint.config.mts | 31 ++++++++++++++-------------- scripts/lint/oxlint/OxlintLinter.ts | 7 +++++++ tests/tooling/lintPipeline.test.ts | 2 +- 5 files changed, 30 insertions(+), 33 deletions(-) diff --git a/config/oxlint/plugins/core-rules.mjs b/config/oxlint/plugins/core-rules.mjs index 6364cfe31d3b..77f210d3e3e4 100644 --- a/config/oxlint/plugins/core-rules.mjs +++ b/config/oxlint/plugins/core-rules.mjs @@ -1,21 +1,12 @@ -// no-invalid-this is left out although ESLint enables it: it calls sourceCode.getJSDocComment, which -// oxlint's bridge throws on as unsupported. +// ESLint core rules oxlint has no native version of (checked against oxlint 1.89.0). dot-notation and +// no-return-await do have typed natives (typescript/dot-notation, typescript/return-await), but those are +// off for JS files, so these copies cover JS. no-invalid-this is left out although ESLint enables it: it +// calls sourceCode.getJSDocComment, which oxlint's bridge throws on as unsupported. import {builtinRules} from 'eslint/use-at-your-own-risk'; import {withEslintDirectiveIdsFor} from '../eslintDirectives.mjs'; -const NAMES = [ - 'no-restricted-syntax', - 'no-unreachable-loop', - 'lines-between-class-members', - 'no-octal', - 'no-octal-escape', - 'no-undef-init', - 'strict', - 'dot-notation', - 'no-dupe-args', - 'no-return-await', -]; +const NAMES = ['no-restricted-syntax', 'lines-between-class-members', 'no-octal', 'no-octal-escape', 'no-undef-init', 'strict', 'dot-notation', 'no-dupe-args', 'no-return-await']; const plugin = { meta: { diff --git a/config/oxlint/ruleNames.mjs b/config/oxlint/ruleNames.mjs index baaeb9801c26..5f012b8cab12 100644 --- a/config/oxlint/ruleNames.mjs +++ b/config/oxlint/ruleNames.mjs @@ -53,8 +53,6 @@ const HOSTED_RULE_ESLINT_ID = { 'no-deprecated-self-closing-jsx': '@typescript-eslint/no-deprecated', }; -const REACT_HOOKS_RULES_UNDER_REACT = new Set(['exhaustive-deps', 'rules-of-hooks']); - const OXLINT_DIAGNOSTIC_CODE = /^([\w@/.-]+)\((.+)\)$/; function hostedRuleNames(eslintPrefix) { @@ -73,7 +71,7 @@ function oxlintCodeToESLintRuleID(code) { if (plugin === 'typescript') { return `@typescript-eslint/${rule}`; } - if (plugin === 'rc' || (plugin === 'react' && REACT_HOOKS_RULES_UNDER_REACT.has(rule))) { + if (plugin === 'rc') { return `react-hooks/${rule}`; } if (plugin === 'hosted') { diff --git a/oxlint.config.mts b/oxlint.config.mts index 134113602ff0..13f62362cf4a 100644 --- a/oxlint.config.mts +++ b/oxlint.config.mts @@ -120,7 +120,6 @@ export default defineConfig({ settings: { react: {version: '19.2.3'}, propWrapperFunctions: ['forbidExtraProps', 'exact', 'Object.freeze'], - progress: {hide: false}, }, ignorePatterns: [ 'oxlint.config.mts', @@ -351,7 +350,8 @@ export default defineConfig({ 'rulesdir/prefer-at': 'off', // ESLint's copy needs the type checker; a type-free rewrite runs for src/**/*.ts(x) below. 'rulesdir/prefer-locale-compare-from-context': 'off', - // Behind config/oxlint/reactCompilerGate.mjs, like ESLint's react-compiler-compat processor. + // Its reports are dropped in files both React Compilers memoize: by ReactCompilerFilter when run + // through scripts/lint, and by config/oxlint/reactCompilerGate.mjs otherwise (editor, raw oxlint). 'rulesdir/no-inline-useOnyx-selector': 'error', 'no-array-constructor': 'error', 'no-bitwise': 'error', @@ -468,8 +468,9 @@ export default defineConfig({ 'react/jsx-no-useless-fragment': 'error', // The native port also fires inside Jest mock factories; `hosted/function-component-definition` runs instead. 'react/function-component-definition': 'off', - // A native Rust rule cannot sit behind the React Compiler gate, and this port anchors on the JSX - // provider instead of the declaration; `hosted/jsx-no-constructed-context-values` runs instead. + // ESLint's own rule runs instead, as `hosted/jsx-no-constructed-context-values`. oxlint's version + // reports on the JSX provider rather than the value's declaration, and the React Compiler gate + // only works on JS plugin rules. 'react/jsx-no-constructed-context-values': 'off', 'react/no-unstable-nested-components': 'error', 'react/no-namespace': 'error', @@ -563,8 +564,16 @@ export default defineConfig({ {selector: 'LabeledStatement', message: 'Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand.'}, {selector: 'WithStatement', message: '`with` is disallowed in strict mode because it makes code impossible to predict and optimize. It is also deprecated.'}, ], - // Hosted because the React Compiler gate can only wrap a JS plugin, and the native port is Rust. + // ESLint's own rule, not oxlint's: ReactCompilerFilter spots the "wrap it in useCallback/useMemo" + // reports by ESLint's wording, which oxlint's version does not use. The React Compiler gate also + // only works on JS plugin rules. 'hosted/exhaustive-deps': 'error', + // Same React Compiler checks as ESLint's react-hooks/* rules, reported under those ids, but run through + // oxc's Rust port of the compiler (config/oxlint/reactCompilerRust.mjs) instead of the JavaScript one in + // eslint-plugin-react-hooks, which would take up most of the oxlint run. oxlint's built-in versions + // (react/refs, react/purity, ...) are not used: an eslint-disable for react/exhaustive-deps or + // react/rules-of-hooks also hides every compiler error in that component + // (https://github.com/oxc-project/oxc/issues/26277). 'rc/refs': 'error', 'rc/set-state-in-effect': 'error', 'rc/set-state-in-render': 'error', @@ -580,7 +589,7 @@ export default defineConfig({ 'no-restricted-exports': ['error', {restrictedNamedExports: ['default', 'then']}], 'import/export': 'error', 'react/require-render-return': 'error', - 'core/no-unreachable-loop': ['error', {ignore: []}], + 'no-unreachable-loop': ['error', {ignore: []}], 'hosted/jsx-no-bind': ['error', {ignoreRefs: true, allowArrowFunctions: true, allowFunctions: false, allowBind: false, ignoreDOMComponents: true}], 'hosted/function-component-definition': ['error', {namedComponents: 'function-declaration', unnamedComponents: 'arrow-function'}], 'hosted/jsx-no-constructed-context-values': 'error', @@ -677,7 +686,7 @@ export default defineConfig({ 'no-undef': ['error', {typeof: false}], // oxlint's name for ESLint's deprecated `no-new-symbol`; see OXLINT_RULE_RENAMES. 'no-new-native-nonconstructor': 'error', - // oxlint has no native dot-notation. + // `typescript/dot-notation` needs type information, so it is off for JS files and ESLint's rule runs here. 'core/dot-notation': ['error', {allowKeywords: true, allowPattern: ''}], 'core/no-dupe-args': 'error', 'core/no-return-await': 'error', @@ -1223,14 +1232,6 @@ export default defineConfig({ 'hosted/no-types': 'off', }, }, - { - files: ['**/*.d.{ts,tsx}'], - rules: { - // ESLint's code-path analyzer throws on declaration files under oxlint's plugin bridge - // (`Cannot read properties of undefined (reading 'parent')`), failing the run. - 'core/no-unreachable-loop': 'off', - }, - }, { // bun-types declares `expect(...).resolves`/`.rejects` as returning `void` even though Bun // requires awaiting them. See https://github.com/oven-sh/bun/pull/23425. diff --git a/scripts/lint/oxlint/OxlintLinter.ts b/scripts/lint/oxlint/OxlintLinter.ts index eba67b27239d..1c6b6725f76d 100644 --- a/scripts/lint/oxlint/OxlintLinter.ts +++ b/scripts/lint/oxlint/OxlintLinter.ts @@ -103,6 +103,8 @@ function availableMemoryBytes(): number { } function defaultShardCount(cpus = os.availableParallelism(), availableBytes = availableMemoryBytes()): number { + // Half the cores for single-threaded shards leaves the rest to the multi-threaded type-aware leg. + // Measured on 14 cores: 7 shards 40-41 s, 4 shards 44 s, 10 shards 46-47 s, 14 shards 49-50 s. const byCpu = Math.floor(cpus / 2); const byMem = Math.floor((availableBytes / GIB - TYPE_AWARE_MEM_GB) / JS_SHARD_MEM_GB); return Math.max(1, Math.min(byCpu, byMem)); @@ -157,12 +159,14 @@ function deriveLegConfigs(config: OxlintConfig): { }), }; delete typeAware.jsPlugins; + // With typeAware off, oxlint skips the type-aware rules in the shards, so only the type-aware process reports them. return { jsPlugins: {...config, options: {...config.options, typeAware: false}}, typeAware, }; } +// Native rules run in both the shards and the type-aware process; mergeShardResults uses this key to report each finding once. function messageKey(message: LintMessage): string { return [message.ruleID, message.severity, message.line, message.column, message.message].join('\u0000'); } @@ -367,6 +371,7 @@ class OxlintLinter extends Linter { remove: () => void; }> { const {jsPlugins, typeAware} = deriveLegConfigs(await loadOxlintConfig(this.options.projectRoot)); + // Written to the repo root: oxlint resolves the `./config/oxlint/...` plugin paths relative to the config file. const jsPluginsConfig = `.oxlintrc.js-plugins.${process.pid}.json`; const typeAwareConfig = `.oxlintrc.type-aware.${process.pid}.json`; fs.writeFileSync(path.join(this.options.projectRoot, jsPluginsConfig), JSON.stringify(jsPlugins)); @@ -387,6 +392,7 @@ class OxlintLinter extends Linter { private async runLegs(legs: OxlintLeg[], notes: string[]): Promise { const outputs = []; + // One process at a time under --fix: the type-aware process lints every file the shards do, so parallel runs would write the same files. if (this.options.fix) { for (const leg of legs) { outputs.push(await this.runOxlint(leg.args)); @@ -450,6 +456,7 @@ class OxlintLinter extends Linter { })); legs.push({ label: 'type-aware process', + // Same files and findings as passing lintedFiles (measured), with a far shorter argv. args: ['-c', configs.typeAwareConfig, ...baseArgs, ...targets], lintedFiles, }); diff --git a/tests/tooling/lintPipeline.test.ts b/tests/tooling/lintPipeline.test.ts index 01ee5b8917e2..a8a20f328bcc 100644 --- a/tests/tooling/lintPipeline.test.ts +++ b/tests/tooling/lintPipeline.test.ts @@ -314,7 +314,7 @@ describe('normalizeOxlintDiagnostics', () => { ['react(jsx-key)', 'react/jsx-key'], ['hosted(naming-convention)', '@typescript-eslint/naming-convention'], ['eslint(no-object-constructor)', 'no-new-object'], - ['react(exhaustive-deps)', 'react-hooks/exhaustive-deps'], + ['react-hooks(rules-of-hooks)', 'react-hooks/rules-of-hooks'], ]; const diagnostics = expected.map(([code]) => oxlintDiagnostic({code})); const [file] = normalizeOxlintDiagnostics(diagnostics, '/repo', ['src/a.ts']); From 1fb79031bda6f0f99aeaeaa64d0e7dd764067342 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Wed, 21 Oct 2026 17:27:25 +0200 Subject: [PATCH 06/37] Re-baseline the oxlint seatbelt after merging main --- config/oxlint/oxlint.seatbelt.tsv | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/config/oxlint/oxlint.seatbelt.tsv b/config/oxlint/oxlint.seatbelt.tsv index fa536ee494ee..c250f4a36541 100644 --- a/config/oxlint/oxlint.seatbelt.tsv +++ b/config/oxlint/oxlint.seatbelt.tsv @@ -113,14 +113,8 @@ "../../src/components/BookTravelButton.tsx" "no-restricted-imports" 1 "../../src/components/ButtonWithDropdownMenu/index.tsx" "react-hooks/set-state-in-effect" 1 "../../src/components/CategoryPicker/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 -"../../src/components/Charts/hooks/useChartInteractions.ts" "@typescript-eslint/no-deprecated/Gesture.Hover" 2 -"../../src/components/Charts/hooks/useChartInteractions.ts" "@typescript-eslint/no-deprecated/Gesture.Race" 2 -"../../src/components/Charts/hooks/useChartInteractions.ts" "@typescript-eslint/no-deprecated/Gesture.Tap" 2 "../../src/components/Charts/hooks/useChartLabelFormats.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/Charts/hooks/useChartLabelMeasurements.ts" "rulesdir/no-raw-typography" 1 -"../../src/components/Charts/PieChart/PieChartContent.tsx" "@typescript-eslint/no-deprecated/Gesture.Hover" 2 -"../../src/components/Charts/PieChart/PieChartContent.tsx" "@typescript-eslint/no-deprecated/Gesture.Race" 2 -"../../src/components/Charts/PieChart/PieChartContent.tsx" "@typescript-eslint/no-deprecated/Gesture.Tap" 2 "../../src/components/Charts/utils/chartFontAssets.native.ts" "@typescript-eslint/no-unsafe-type-assertion" 13 "../../src/components/Charts/utils/chartFontAssets.ts" "@typescript-eslint/no-unsafe-type-assertion" 13 "../../src/components/Charts/utils/chartFontsCache.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 @@ -318,8 +312,6 @@ "../../src/components/Navigation/QuickCreationActionsBar/index.tsx" "no-restricted-imports" 1 "../../src/components/NumberWithSymbolForm.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/NumberWithSymbolForm.tsx" "react-hooks/set-state-in-effect" 2 -"../../src/components/NumericInput/primitives/NumericInputContainer.tsx" "@typescript-eslint/no-unsafe-assignment" 1 -"../../src/components/NumericInput/primitives/NumericSymbolButton.tsx" "@typescript-eslint/no-unsafe-assignment" 2 "../../src/components/OfflineWithFeedback.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../src/components/OfflineWithFeedback.tsx" "import/no-cycle" 1 "../../src/components/Onfido/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 @@ -505,10 +497,6 @@ "../../src/components/SubStepForms/RegistrationNumberStep.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 "../../src/components/SwipeableView/index.native.tsx" "react-hooks/refs" 2 "../../src/components/SymbolButton.tsx" "no-restricted-syntax" 1 -"../../src/components/Table/columnResize/applyColumnWidthOverrides.ts" "@typescript-eslint/no-redundant-type-constituents" 1 -"../../src/components/Table/columnResize/applyColumnWidthOverrides.ts" "@typescript-eslint/no-unsafe-argument" 1 -"../../src/components/Table/columnResize/applyColumnWidthOverrides.ts" "@typescript-eslint/no-unsafe-assignment" 1 -"../../src/components/Table/columnResize/applyColumnWidthOverrides.ts" "@typescript-eslint/no-unsafe-member-access" 1 "../../src/components/Table/middlewares/filtering.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/Table/Table.tsx" "@typescript-eslint/no-unsafe-type-assertion" 4 "../../src/components/Table/TableBody.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 @@ -2285,6 +2273,8 @@ "../../tests/ui/components/MoneyReportHeaderFocusSelectionModeTest.tsx" "no-restricted-imports" 1 "../../tests/ui/components/MoneyReportHeaderSelectionModeTest.tsx" "no-restricted-imports" 1 "../../tests/ui/components/MoneyRequestHeaderSecondaryActionsTest.tsx" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/ui/components/SelectionToolbarSubmitToPopoverTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/ui/components/SelectionToolbarSubmitToPopoverTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../tests/ui/components/SettlementButtonTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 7 "../../tests/ui/ComposerImperativeRefTest.tsx" "@typescript-eslint/no-deprecated/keyCode" 1 "../../tests/ui/ComposerTest.tsx" "rulesdir/no-raw-typography" 1 @@ -2415,6 +2405,8 @@ "../../tests/unit/hooks/useDeleteTransactionsInvoiceOfflineTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 4 "../../tests/unit/hooks/useDismissOnMoneyRequestReportRemoval.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 "../../tests/unit/hooks/useExpenseActionsInvoiceDeleteTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../tests/unit/hooks/useExpenseActionsSingleExpenseTableViewDeleteTest.tsx" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/unit/hooks/useExpenseActionsSingleExpenseTableViewDeleteTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../tests/unit/hooks/useExpenseSubmission.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 6 "../../tests/unit/hooks/useFeedKeysWithAssignedCards.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 5 "../../tests/unit/hooks/useFilesValidation.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 4 @@ -2434,6 +2426,7 @@ "../../tests/unit/hooks/useSeedMyExpensesSearch.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/unit/hooks/useSelectedTransactionsActions.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 4 "../../tests/unit/hooks/useSelectionModeReportActions.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../tests/unit/hooks/useSingleExpenseReportView.test.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 "../../tests/unit/hooks/useSplitEffectivePolicy.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/unit/hooks/useSplitParticipants.test.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/unit/hooks/useTimeSensitiveAddBankAccount.test.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 @@ -2539,6 +2532,7 @@ "../../tests/unit/SearchAutocompleteListTest.tsx" "no-restricted-imports" 1 "../../tests/unit/SearchEditMultipleUtilsTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 "../../tests/unit/SequentialQueueTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../tests/unit/shouldShowPushNotificationTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 "../../tests/unit/showReportActionNotificationTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 3 "../../tests/unit/SidebarLinksTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/unit/SidebarLinksTest.tsx" "react/button-has-type" 1 From 9090e32a2f3bedf0af6a770396f732a68072651b Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Wed, 21 Oct 2026 18:15:31 +0200 Subject: [PATCH 07/37] Run the oxlint React Compiler gate inside the lint pipeline again --- config/oxlint/reactCompilerGate.mjs | 9 +++------ oxlint.config.mts | 8 +++----- scripts/lint/oxlint/OxlintLinter.ts | 6 +----- 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/config/oxlint/reactCompilerGate.mjs b/config/oxlint/reactCompilerGate.mjs index e8df84b26b11..5443a5f70fc3 100644 --- a/config/oxlint/reactCompilerGate.mjs +++ b/config/oxlint/reactCompilerGate.mjs @@ -26,13 +26,10 @@ function resolveMessage(rule, descriptor) { return template.replaceAll(/\{\{\s*(\w+)\s*\}\}/g, (match, key) => descriptor.data?.[key] ?? match); } -// Only runs outside scripts/lint need this (the editor, raw `oxlint`): without it they flag these rules -// in files the React Compiler memoizes, and the fix they suggest (useCallback/useMemo) is banned there. +// Drops these rules' reports in files both React Compilers memoize, where the fix they suggest +// (useCallback/useMemo) is banned. Under scripts/lint this also leaves ReactCompilerFilter nothing to +// compile: the compile runs here, inside the parallel JS-plugin shards, instead of after them. function withGating(rule, shouldGate) { - // scripts/lint already drops these messages in ReactCompilerFilter, so skip a second compile. - if (process.env.LINT_PIPELINE === '1') { - return rule; - } return { ...rule, create(context) { diff --git a/oxlint.config.mts b/oxlint.config.mts index 13f62362cf4a..cdfc2d5cf4e4 100644 --- a/oxlint.config.mts +++ b/oxlint.config.mts @@ -350,8 +350,7 @@ export default defineConfig({ 'rulesdir/prefer-at': 'off', // ESLint's copy needs the type checker; a type-free rewrite runs for src/**/*.ts(x) below. 'rulesdir/prefer-locale-compare-from-context': 'off', - // Its reports are dropped in files both React Compilers memoize: by ReactCompilerFilter when run - // through scripts/lint, and by config/oxlint/reactCompilerGate.mjs otherwise (editor, raw oxlint). + // config/oxlint/reactCompilerGate.mjs drops its reports in files both React Compilers memoize. 'rulesdir/no-inline-useOnyx-selector': 'error', 'no-array-constructor': 'error', 'no-bitwise': 'error', @@ -564,9 +563,8 @@ export default defineConfig({ {selector: 'LabeledStatement', message: 'Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand.'}, {selector: 'WithStatement', message: '`with` is disallowed in strict mode because it makes code impossible to predict and optimize. It is also deprecated.'}, ], - // ESLint's own rule, not oxlint's: ReactCompilerFilter spots the "wrap it in useCallback/useMemo" - // reports by ESLint's wording, which oxlint's version does not use. The React Compiler gate also - // only works on JS plugin rules. + // ESLint's own rule, not oxlint's: the React Compiler gate spots the "wrap it in useCallback/useMemo" + // reports by ESLint's wording, which oxlint's version does not use, and it only works on JS plugin rules. 'hosted/exhaustive-deps': 'error', // Same React Compiler checks as ESLint's react-hooks/* rules, reported under those ids, but run through // oxc's Rust port of the compiler (config/oxlint/reactCompilerRust.mjs) instead of the JavaScript one in diff --git a/scripts/lint/oxlint/OxlintLinter.ts b/scripts/lint/oxlint/OxlintLinter.ts index 1c6b6725f76d..f6f6a3a39eb5 100644 --- a/scripts/lint/oxlint/OxlintLinter.ts +++ b/scripts/lint/oxlint/OxlintLinter.ts @@ -358,11 +358,7 @@ class OxlintLinter extends Linter { } private runOxlint(args: string[]) { - return $`${OXLINT_BIN} ${args}` - .cwd(this.options.projectRoot) - .env({...process.env, LINT_PIPELINE: '1'}) - .nothrow() - .quiet(); + return $`${OXLINT_BIN} ${args}`.cwd(this.options.projectRoot).nothrow().quiet(); } private async writeLegConfigs(): Promise<{ From 71aede4c1aee3bf2629a51f378b88fd7144ebf8e Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Thu, 22 Oct 2026 09:14:41 +0200 Subject: [PATCH 08/37] Report oxlint parse errors like ESLint instead of failing the whole run --- scripts/lint/oxlint/OxlintLinter.ts | 23 +++++++++++------- tests/tooling/lintPipeline.test.ts | 37 ++++++++++++++++++++++++----- 2 files changed, 46 insertions(+), 14 deletions(-) diff --git a/scripts/lint/oxlint/OxlintLinter.ts b/scripts/lint/oxlint/OxlintLinter.ts index f6f6a3a39eb5..4cb09cac1075 100644 --- a/scripts/lint/oxlint/OxlintLinter.ts +++ b/scripts/lint/oxlint/OxlintLinter.ts @@ -76,6 +76,7 @@ type OxlintLeg = { const FATAL_EXIT_CODE = 2; const SIGNAL_EXIT_CODE = 128; +const JS_PLUGIN_CRASH_MESSAGE = 'Error running JS plugin'; const OXLINT_WARNING_SEVERITIES = new Set(['warning', 'advice']); const GIB = 1073741824; @@ -216,7 +217,7 @@ function toAbsolute(projectRoot: string, filename: string): string { return path.isAbsolute(filename) ? filename : path.resolve(projectRoot, filename); } -function toLintMessage(projectRoot: string, diagnostic: OxlintDiagnostic, ruleID: string): LintMessage { +function toLintMessage(projectRoot: string, diagnostic: OxlintDiagnostic, ruleID: string | null): LintMessage { const span = diagnostic.labels?.at(0)?.span; return { filePath: toAbsolute(projectRoot, diagnostic.filename), @@ -228,6 +229,12 @@ function toLintMessage(projectRoot: string, diagnostic: OxlintDiagnostic, ruleID }; } +/** A codeless diagnostic that is not a plugin crash is a parse error; ESLint reports those with `ruleId: null`. */ +function toParseErrorMessage(projectRoot: string, diagnostic: OxlintDiagnostic): LintMessage { + const message = toLintMessage(projectRoot, diagnostic, null); + return {...message, message: `Parsing error: ${message.message}`}; +} + /** `lintedFiles` comes from a separate `--debug=files` pass because oxlint's JSON omits clean files. */ function normalizeOxlintDiagnostics(diagnostics: OxlintDiagnostic[], projectRoot: string, lintedFiles: readonly string[]): LintFileResult[] { const byFile = new Map(); @@ -238,7 +245,7 @@ function normalizeOxlintDiagnostics(diagnostics: OxlintDiagnostic[], projectRoot for (const diagnostic of diagnostics) { const filePath = toAbsolute(projectRoot, diagnostic.filename); const messages = byFile.get(filePath) ?? []; - messages.push(toLintMessage(projectRoot, diagnostic, oxlintCodeToESLintRuleID(diagnostic.code ?? ''))); + messages.push(diagnostic.code ? toLintMessage(projectRoot, diagnostic, oxlintCodeToESLintRuleID(diagnostic.code)) : toParseErrorMessage(projectRoot, diagnostic)); byFile.set(filePath, messages); } @@ -311,14 +318,14 @@ function parseOxlintStdout(stdout: string, stderr: string, exitCode: number, pro return fatal('Oxlint JSON output has no `diagnostics` array.', stdout, stderr, exitCode); } - // A diagnostic with no `code` is a crashing JS plugin, not a finding. - const codeless = parsed.diagnostics.filter((diagnostic) => !diagnostic.code); - if (codeless.length > 0) { - const sample = codeless + // Parse errors and JS plugin crashes both come without a `code`; only a crash says so in its message. + const crashes = parsed.diagnostics.filter((diagnostic) => !diagnostic.code && diagnostic.message.startsWith(JS_PLUGIN_CRASH_MESSAGE)); + if (crashes.length > 0) { + const sample = crashes .slice(0, 5) - .map((diagnostic) => ` ${diagnostic.filename}: ${diagnostic.message}`) + .map((diagnostic) => ` ${diagnostic.filename}: ${diagnostic.message.split('\n').slice(0, 3).join(' ')}`) .join('\n'); - return fatal(`Oxlint reported ${codeless.length} diagnostic(s) with no rule code, which means a JS plugin threw:\n${sample}`, '', stderr, exitCode); + return fatal(`Oxlint reported ${crashes.length} JS plugin crash(es):\n${sample}`, '', stderr, exitCode); } return { diff --git a/tests/tooling/lintPipeline.test.ts b/tests/tooling/lintPipeline.test.ts index a8a20f328bcc..556a5415dddc 100644 --- a/tests/tooling/lintPipeline.test.ts +++ b/tests/tooling/lintPipeline.test.ts @@ -375,14 +375,37 @@ describe('parseOxlintStdout', () => { expect(parsed.stderr).toContain('Failed to parse Oxlint JSON output'); }); - it('treats a diagnostic with no rule code as a crashing JS plugin, not a finding', () => { - const parsed = parseOxlintStdout(oxlintStdout([oxlintDiagnostic({code: undefined, message: 'plugin blew up'})]), '', 1, '/repo', ['src/a.ts']); + it('treats a crashing JS plugin as fatal, not a finding', () => { + // Given the codeless, label-less diagnostic oxlint 1.89.0 emits when a JS plugin rule throws + const crash = oxlintDiagnostic({code: undefined, labels: [], message: 'Error running JS plugin.\nFile path: /repo/src/a.ts\nError: plugin blew up'}); + + // When the report is parsed + const parsed = parseOxlintStdout(oxlintStdout([crash]), '', 1, '/repo', ['src/a.ts']); + + // Then the run is fatal, because the crashed rule checked nothing in that file expect(parsed.exitCode).toBe(2); expect(parsed.files).toEqual([]); - expect(parsed.stderr).toContain('no rule code'); + expect(parsed.stderr).toContain('JS plugin crash'); expect(parsed.stderr).toContain('plugin blew up'); }); + it('reports a parse error like ESLint does and keeps the other findings', () => { + // Given oxlint's codeless parse error in one file and a normal finding in another + const parseError = oxlintDiagnostic({code: undefined, message: 'Unexpected token', filename: 'src/bad.ts', labels: [{span: {offset: 6, length: 1, line: 1, column: 7}}]}); + const finding = oxlintDiagnostic({filename: 'src/good.ts'}); + + // When the report is parsed + const parsed = parseOxlintStdout(oxlintStdout([parseError, finding], 2), '', 1, '/repo', ['src/bad.ts', 'src/good.ts']); + + // Then, as with ESLint's fatal `ruleId: null` message, the parse error is an error and the run goes on + expect(parsed.exitCode).toBe(1); + const byFile = Object.fromEntries(parsed.files.map((file) => [file.filePath, file.messages])); + expect(byFile['/repo/src/bad.ts']).toEqual([ + {filePath: '/repo/src/bad.ts', ruleID: null, severity: LINT_SEVERITY.ERROR, message: 'Parsing error: Unexpected token', line: 1, column: 7}, + ]); + expect(byFile['/repo/src/good.ts']?.map((message) => message.ruleID)).toEqual(['no-console']); + }); + it('rejects a JSON object that is not an oxlint report', () => { expect(parseOxlintStdout('{"oops": true}', '', 1, '/repo', []).exitCode).toBe(2); }); @@ -522,7 +545,7 @@ describe('oxlint sharding', () => { expect(isTransientFailure('Error running tsgolint: "exit status: exit status: 1"\n', 1)).toBe(true); expect(isTransientFailure(oxlintStdout([]), 137)).toBe(true); expect(isTransientFailure(oxlintStdout([]), 134)).toBe(true); - expect(isTransientFailure(oxlintStdout([oxlintDiagnostic({code: undefined, message: 'plugin blew up'})]), 1)).toBe(false); + expect(isTransientFailure(oxlintStdout([oxlintDiagnostic({code: undefined, labels: [], message: 'Error running JS plugin.\nError: plugin blew up'})]), 1)).toBe(false); expect(isTransientFailure(oxlintStdout([oxlintDiagnostic()], 1), 1)).toBe(false); expect(isTransientFailure(oxlintStdout([]), 0)).toBe(false); }); @@ -535,7 +558,9 @@ describe('oxlint sharding', () => { it('a crashed JS plugin in one shard is fatal and dominates clean shards', () => { const clean = parseOxlintStdout(oxlintStdout([oxlintDiagnostic()], 1), '', 1, '/repo', ['src/a.ts']); - const crash = parseOxlintStdout(oxlintStdout([oxlintDiagnostic({code: undefined, message: 'plugin blew up'})]), '', 1, '/repo', ['src/b.ts']); + const crash = parseOxlintStdout(oxlintStdout([oxlintDiagnostic({code: undefined, labels: [], message: 'Error running JS plugin.\nError: plugin blew up'})]), '', 1, '/repo', [ + 'src/b.ts', + ]); const merged = mergeShardResults([clean, crash]); expect(merged.exitCode).toBe(2); expect(merged.stderr).toContain('plugin blew up'); @@ -545,7 +570,7 @@ describe('oxlint sharding', () => { expect(producedNoJSON('')).toBe(true); expect(producedNoJSON('Failed to parse oxlint configuration file.\n x Rule not found\n')).toBe(true); - expect(producedNoJSON(oxlintStdout([oxlintDiagnostic({code: undefined, message: 'plugin blew up'})]))).toBe(false); + expect(producedNoJSON(oxlintStdout([oxlintDiagnostic({code: undefined, labels: [], message: 'Error running JS plugin.\nError: plugin blew up'})]))).toBe(false); expect(producedNoJSON(oxlintStdout([oxlintDiagnostic()], 1))).toBe(false); expect(producedNoJSON(oxlintStdout([]))).toBe(false); From fe1b3e248c6a96450125593f8dfaf1edc5d0a3fe Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Thu, 22 Oct 2026 09:15:25 +0200 Subject: [PATCH 09/37] Give oxlint ESLint's import resolver settings so hosted import rules resolve TypeScript --- oxlint.config.mts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/oxlint.config.mts b/oxlint.config.mts index cdfc2d5cf4e4..92a15a7a8695 100644 --- a/oxlint.config.mts +++ b/oxlint.config.mts @@ -120,6 +120,10 @@ export default defineConfig({ settings: { react: {version: '19.2.3'}, propWrapperFunctions: ['forbidExtraProps', 'exact', 'Object.freeze'], + // Read by the hosted eslint-plugin-import rules; without them `no-useless-path-segments` and + // `no-relative-packages` cannot resolve .ts/.tsx targets and stay silent. Same values as ESLint's. + 'import/resolver': {node: {extensions: ['.mjs', '.js', '.jsx', '.json', '.ts', '.tsx', '.d.ts', '.cts', '.mts']}}, + 'import/extensions': ['.js', '.mjs', '.jsx', '.ts', '.tsx', '.d.ts', '.cts', '.mts'], }, ignorePatterns: [ 'oxlint.config.mts', From 221d0b6403266a982db3e9403c2f042af89ec7a7 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Thu, 22 Oct 2026 09:15:42 +0200 Subject: [PATCH 10/37] Skip React Compiler diagnostics without a location, as eslint-plugin-react-hooks does --- config/oxlint/reactCompilerRust.mjs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/config/oxlint/reactCompilerRust.mjs b/config/oxlint/reactCompilerRust.mjs index 2728ccd4527e..ae642fb8b56b 100644 --- a/config/oxlint/reactCompilerRust.mjs +++ b/config/oxlint/reactCompilerRust.mjs @@ -198,11 +198,15 @@ function analyze(filename, sourceText) { ); } const label = error.labels?.[0]; + // eslint-plugin-react-hooks skips a diagnostic with no location; reporting it at 1:0 would be oxlint-only. + if (!label) { + continue; + } diagnostics.push({ ruleName, loc: { - start: offsetToPoint(label?.start ?? 0), - end: offsetToPoint(label?.end ?? label?.start ?? 0), + start: offsetToPoint(label.start), + end: offsetToPoint(label.end ?? label.start), }, message: buildMessage(category, error), }); From 7c4ae503ede458aeea1365cc5ca115fd7f9d2b99 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Thu, 22 Oct 2026 10:27:56 +0200 Subject: [PATCH 11/37] Match ESLint directive placement for hosted rules: block comments only, compared by column --- config/oxlint/eslintDirectives.mjs | 37 +++++++++++++++++------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/config/oxlint/eslintDirectives.mjs b/config/oxlint/eslintDirectives.mjs index a626217e8d8d..1f8047da5852 100644 --- a/config/oxlint/eslintDirectives.mjs +++ b/config/oxlint/eslintDirectives.mjs @@ -32,6 +32,11 @@ function parseDirectives(comments) { if (!parsed || start == null || end == null) { continue; } + // ESLint reads `eslint-disable` and `eslint-enable` only from block comments. + const isBlockKind = parsed.kind === 'eslint-disable' || parsed.kind === 'eslint-enable'; + if (isBlockKind && comment.type !== 'Block') { + continue; + } for (const ruleId of parsed.ruleIds) { if (parsed.kind === 'eslint-disable-next-line' || parsed.kind === 'eslint-disable-line') { // ESLint ignores a multi-line `-line` directive but applies a multi-line `-next-line` @@ -44,7 +49,7 @@ function parseDirectives(comments) { } lines.get(ruleId).add(parsed.kind === 'eslint-disable-line' ? start : end + 1); } else { - blocks.push({line: start, ruleId, disable: parsed.kind === 'eslint-disable'}); + blocks.push({line: start, column: comment.loc.start.column ?? 0, ruleId, disable: parsed.kind === 'eslint-disable'}); } } } @@ -60,32 +65,32 @@ function directivesFor(filename, comments) { return cache.get(key); } -// As in ESLint, the last block directive at or before the line that names the rule (or no rule) wins. -function isSuppressed(directives, eslintRuleId, line) { - if (line == null) { +// As in ESLint, the last block directive before the report, by line and then column, that names the +// rule (or no rule) wins. +function isSuppressed(directives, eslintRuleId, position) { + if (position == null) { return false; } + const {line, column} = position; if (directives.lines.get(eslintRuleId)?.has(line) || directives.lines.get(ALL_RULES)?.has(line)) { return true; } let disabled = false; for (const block of directives.blocks) { - if (block.line > line || (block.ruleId !== ALL_RULES && block.ruleId !== eslintRuleId)) { + const isAfterReport = block.line > line || (block.line === line && block.column > column); + if (isAfterReport || (block.ruleId !== ALL_RULES && block.ruleId !== eslintRuleId)) { continue; } - if (block.disable) { - disabled = true; - } else if (block.line < line) { - disabled = false; - } + disabled = block.disable; } return disabled; } -function reportLine(args) { +function reportPosition(args) { const first = args[0]; - const candidates = [first?.loc?.start?.line, first?.loc?.line, first?.node?.loc?.start?.line, first?.node?.loc?.line]; - return candidates.find((line) => typeof line === 'number') ?? null; + const candidates = [first?.loc?.start, first?.loc, first?.node?.loc?.start, first?.node?.loc]; + const point = candidates.find((candidate) => typeof candidate?.line === 'number'); + return point ? {line: point.line, column: typeof point.column === 'number' ? point.column : 0} : null; } function withEslintDirectiveIds(rule, eslintRuleId) { @@ -96,11 +101,11 @@ function withEslintDirectiveIds(rule, eslintRuleId) { report: { // Keeps every argument: ESLint also accepts the legacy `report(node, message)` form. value(...args) { - const line = reportLine(args); - if (line != null) { + const position = reportPosition(args); + if (position != null) { const sourceCode = context.sourceCode ?? context.getSourceCode(); const filename = context.filename ?? context.getFilename(); - if (isSuppressed(directivesFor(filename, sourceCode.getAllComments()), eslintRuleId, line)) { + if (isSuppressed(directivesFor(filename, sourceCode.getAllComments()), eslintRuleId, position)) { return; } } From ff17244169a644133f2fad6e4de85649bf755876 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Thu, 22 Oct 2026 10:28:38 +0200 Subject: [PATCH 12/37] Compile .mts and .cts files as TypeScript in the oxlint React Compiler bridge --- config/oxlint/reactCompilerRust.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/oxlint/reactCompilerRust.mjs b/config/oxlint/reactCompilerRust.mjs index ae642fb8b56b..212a57bce363 100644 --- a/config/oxlint/reactCompilerRust.mjs +++ b/config/oxlint/reactCompilerRust.mjs @@ -67,7 +67,7 @@ function getLang(ext) { if (ext === 'tsx') { return 'tsx'; } - if (ext === 'ts') { + if (ext === 'ts' || ext === 'mts' || ext === 'cts') { return 'ts'; } return 'jsx'; From c6d452c53304c809fdc2f4ea0b37f998b61b2554 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Thu, 22 Oct 2026 11:02:49 +0200 Subject: [PATCH 13/37] Run oxlint --fix in one process, replace OXLINT_SHARDS with --shards=auto|N, drop unused OXLINT_THREADS --- .github/workflows/oxlint.yml | 4 +--- scripts/lint/index.ts | 8 ++++---- scripts/lint/oxlint/OxlintLinter.ts | 32 +++++++++++++---------------- tests/tooling/lintPipeline.test.ts | 9 +++++--- 4 files changed, 25 insertions(+), 28 deletions(-) diff --git a/.github/workflows/oxlint.yml b/.github/workflows/oxlint.yml index ad158c3efef6..d1e4a218e6b0 100644 --- a/.github/workflows/oxlint.yml +++ b/.github/workflows/oxlint.yml @@ -29,9 +29,7 @@ jobs: id: oxlint continue-on-error: true # oxlint runs JS plugins on one thread (oxc#26621), so OxlintLinter splits them into 4 shards on this 8 vCPU runner. - env: - OXLINT_SHARDS: 4 - run: npm run lint -- --linter=oxlint + run: npm run lint -- --linter=oxlint --shards=4 - name: Report the Oxlint result without failing the job run: | diff --git a/scripts/lint/index.ts b/scripts/lint/index.ts index f8df570bd40a..e471d1ad74ab 100644 --- a/scripts/lint/index.ts +++ b/scripts/lint/index.ts @@ -85,9 +85,9 @@ const cli = new CLI({ parse: parseFormatName, }, shards: { - description: 'Oxlint only: processes to fan the JS plugins across (0 = auto by cores and free memory; 1 = single process)', - default: 0, - parse: (value: string) => Number.parseInt(value, 10), + description: 'Oxlint only: processes to fan the JS plugins across, a positive integer or `auto` (by cores and free memory)', + default: 'auto', + parse: (value: string) => value, }, }, positionalArgs: [ @@ -107,7 +107,7 @@ const bench = new Bench(); function makeLinter(name: LinterName): Linter { if (name === 'oxlint') { - return new OxlintLinter({projectRoot, fix: cli.flags.fix, shards: cli.namedArgs.shards > 0 ? String(cli.namedArgs.shards) : undefined}); + return new OxlintLinter({projectRoot, fix: cli.flags.fix, shards: cli.namedArgs.shards}); } return new ESLintLinter({ projectRoot, diff --git a/scripts/lint/oxlint/OxlintLinter.ts b/scripts/lint/oxlint/OxlintLinter.ts index 4cb09cac1075..bc8b647e2a9d 100644 --- a/scripts/lint/oxlint/OxlintLinter.ts +++ b/scripts/lint/oxlint/OxlintLinter.ts @@ -64,7 +64,6 @@ type OxlintConfig = { type OxlintLinterOptions = { projectRoot: string; fix: boolean; - threads?: string; shards?: string; }; @@ -111,12 +110,17 @@ function defaultShardCount(cpus = os.availableParallelism(), availableBytes = av return Math.max(1, Math.min(byCpu, byMem)); } -function resolveShardCount(override: string | undefined): number { - if (override === undefined || override.trim() === '') { +// Unset means auto, as does `auto`; anything else must be a positive integer. +function resolveShardCount(shards: string | undefined): number { + const value = shards?.trim() ?? 'auto'; + if (value === 'auto') { return defaultShardCount(); } - const parsed = Number.parseInt(override, 10); - return Number.isFinite(parsed) && parsed >= 1 ? parsed : 1; + const parsed = Number(value); + if (!Number.isInteger(parsed) || parsed < 1) { + throw new Error(`--shards must be a positive integer or "auto"; got "${shards}".`); + } + return parsed; } function shardFiles(files: readonly string[], count: number): string[][] { @@ -394,15 +398,7 @@ class OxlintLinter extends Linter { } private async runLegs(legs: OxlintLeg[], notes: string[]): Promise { - const outputs = []; - // One process at a time under --fix: the type-aware process lints every file the shards do, so parallel runs would write the same files. - if (this.options.fix) { - for (const leg of legs) { - outputs.push(await this.runOxlint(leg.args)); - } - } else { - outputs.push(...(await Promise.all(legs.map((leg) => this.runOxlint(leg.args))))); - } + const outputs = await Promise.all(legs.map((leg) => this.runOxlint(leg.args))); const parsed: LinterResult[] = []; for (const [index, leg] of legs.entries()) { @@ -438,15 +434,15 @@ class OxlintLinter extends Linter { baseArgs.push('--fix'); } - const shardCount = resolveShardCount(this.options.shards ?? process.env.OXLINT_SHARDS); let plan: string; let merged: LinterResult; try { + // --fix runs one process: a shard's fixes would shift the lines the type-aware leg then + // reports, so the merge would keep one finding twice under two columns. + const shardCount = this.options.fix ? 1 : resolveShardCount(this.options.shards); if (shardCount <= 1) { - const threads = this.options.threads ?? process.env.OXLINT_THREADS; - const args = threads ? [`--threads=${threads}`, ...baseArgs] : baseArgs; plan = 'Oxlint plan: 1 process, stock config.'; - merged = await this.runLegs([{label: 'process', args: [...args, ...lintedFiles], lintedFiles}], notes); + merged = await this.runLegs([{label: 'process', args: [...baseArgs, ...lintedFiles], lintedFiles}], notes); } else { const buckets = shardFiles(lintedFiles, shardCount); plan = `Oxlint plan: ${buckets.length} JS-plugin shards plus 1 type-aware process (cores ${os.availableParallelism()}, available memory ${(availableMemoryBytes() / GIB).toFixed(1)} GB).`; diff --git a/tests/tooling/lintPipeline.test.ts b/tests/tooling/lintPipeline.test.ts index 556a5415dddc..951aa0e9cea5 100644 --- a/tests/tooling/lintPipeline.test.ts +++ b/tests/tooling/lintPipeline.test.ts @@ -445,10 +445,13 @@ describe('oxlint sharding', () => { it('auto-shards by machine size and honours explicit overrides', () => { expect(resolveShardCount(undefined)).toBeGreaterThanOrEqual(1); - expect(resolveShardCount('')).toBeGreaterThanOrEqual(1); + expect(() => resolveShardCount('')).toThrow('--shards'); expect(resolveShardCount('6')).toBe(6); - expect(resolveShardCount('0')).toBe(1); - expect(resolveShardCount('not-a-number')).toBe(1); + expect(resolveShardCount('auto')).toBe(resolveShardCount(undefined)); + expect(() => resolveShardCount('0')).toThrow('--shards'); + expect(() => resolveShardCount('not-a-number')).toThrow('--shards'); + expect(() => resolveShardCount('-2')).toThrow('--shards'); + expect(() => resolveShardCount('1.5')).toThrow('--shards'); }); it('sizes the JS-plugin shards by half the cores, minus the memory one type-aware process needs', () => { From 69bef7b16d7691466b1dcce1fd67229a11cfbef1 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Thu, 22 Oct 2026 12:27:48 +0200 Subject: [PATCH 14/37] Treat a codeless oxlint diagnostic with no location as a plugin crash, whatever its wording --- scripts/lint/oxlint/OxlintLinter.ts | 9 +++++++-- tests/tooling/lintPipeline.test.ts | 13 +++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/scripts/lint/oxlint/OxlintLinter.ts b/scripts/lint/oxlint/OxlintLinter.ts index bc8b647e2a9d..47a21e652bb9 100644 --- a/scripts/lint/oxlint/OxlintLinter.ts +++ b/scripts/lint/oxlint/OxlintLinter.ts @@ -233,6 +233,10 @@ function toLintMessage(projectRoot: string, diagnostic: OxlintDiagnostic, ruleID }; } +function isJSPluginCrash(diagnostic: OxlintDiagnostic): boolean { + return !diagnostic.code && (diagnostic.message.startsWith(JS_PLUGIN_CRASH_MESSAGE) || !diagnostic.labels?.length); +} + /** A codeless diagnostic that is not a plugin crash is a parse error; ESLint reports those with `ruleId: null`. */ function toParseErrorMessage(projectRoot: string, diagnostic: OxlintDiagnostic): LintMessage { const message = toLintMessage(projectRoot, diagnostic, null); @@ -322,8 +326,9 @@ function parseOxlintStdout(stdout: string, stderr: string, exitCode: number, pro return fatal('Oxlint JSON output has no `diagnostics` array.', stdout, stderr, exitCode); } - // Parse errors and JS plugin crashes both come without a `code`; only a crash says so in its message. - const crashes = parsed.diagnostics.filter((diagnostic) => !diagnostic.code && diagnostic.message.startsWith(JS_PLUGIN_CRASH_MESSAGE)); + // Parse errors and JS plugin crashes both come without a `code`. A crash has no location and says so in its + // message; checking both keeps a reworded crash message from passing as a parse error. + const crashes = parsed.diagnostics.filter(isJSPluginCrash); if (crashes.length > 0) { const sample = crashes .slice(0, 5) diff --git a/tests/tooling/lintPipeline.test.ts b/tests/tooling/lintPipeline.test.ts index 951aa0e9cea5..3790c409d463 100644 --- a/tests/tooling/lintPipeline.test.ts +++ b/tests/tooling/lintPipeline.test.ts @@ -389,6 +389,19 @@ describe('parseOxlintStdout', () => { expect(parsed.stderr).toContain('plugin blew up'); }); + it('treats a codeless diagnostic with no location as a crash even if oxlint rewords the message', () => { + // Given a crash whose message no longer starts with today's wording; every oxc parse error carries a label + const crash = oxlintDiagnostic({code: undefined, labels: [], message: 'JS plugin failed: plugin blew up'}); + + // When the report is parsed + const parsed = parseOxlintStdout(oxlintStdout([crash]), '', 1, '/repo', ['src/a.ts']); + + // Then it is still fatal rather than a "Parsing error" at 0:0 + expect(parsed.exitCode).toBe(2); + expect(parsed.files).toEqual([]); + expect(parsed.stderr).toContain('plugin blew up'); + }); + it('reports a parse error like ESLint does and keeps the other findings', () => { // Given oxlint's codeless parse error in one file and a normal finding in another const parseError = oxlintDiagnostic({code: undefined, message: 'Unexpected token', filename: 'src/bad.ts', labels: [{span: {offset: 6, length: 1, line: 1, column: 7}}]}); From 9e50d9de88241ae2c90160ae00aeea9328322b9e Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Thu, 22 Oct 2026 12:46:18 +0200 Subject: [PATCH 15/37] Absorb new oxlint findings into its baseline on main while oxlint is a shadow job --- .github/workflows/oxlint.yml | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/.github/workflows/oxlint.yml b/.github/workflows/oxlint.yml index d1e4a218e6b0..6c152d825dab 100644 --- a/.github/workflows/oxlint.yml +++ b/.github/workflows/oxlint.yml @@ -29,39 +29,48 @@ jobs: id: oxlint continue-on-error: true # oxlint runs JS plugins on one thread (oxc#26621), so OxlintLinter splits them into 4 shards on this 8 vCPU runner. + # While oxlint is a non-blocking shadow job, the post-merge run on main absorbs new findings into the + # baseline as well as removing fixed ones, so later PRs are only warned about findings they add. + # Runs started by OSBotify's own baseline commit only tighten, so a finding that flickers between + # runs cannot make every run commit again. Remove this when oxlint becomes the blocking linter: + # from then on main only tightens, like ESLint. + env: + SEATBELT_INCREASE: ${{ github.event_name == 'push' && github.event.head_commit.author.name != 'OSBotify' && 'all' || '' }} run: npm run lint -- --linter=oxlint --shards=4 - name: Report the Oxlint result without failing the job run: | - if [[ "${{ steps.oxlint.outcome }}" == "success" ]]; then + if [[ "${{ steps.oxlint.outcome }}" == "success" && "${{ github.event_name }}" == "push" ]]; then + echo "::notice::Shadow mode: new oxlint findings from this merge were added to the baseline instead of reported, so later PRs only see findings they introduce. ESLint is still the blocking check." + elif [[ "${{ steps.oxlint.outcome }}" == "success" ]]; then echo "::notice::Oxlint found no errors above its baseline." else echo "::warning::Oxlint reported errors above its baseline. Non-blocking during the migration; see the step log." fi - - name: Check for tightened oxlint-seatbelt baseline + - name: Check for an updated oxlint-seatbelt baseline id: seatbelt_diff if: github.event_name == 'push' run: | if git diff --quiet config/oxlint/oxlint.seatbelt.tsv; then - echo "tightened=false" >> "$GITHUB_OUTPUT" + echo "updated=false" >> "$GITHUB_OUTPUT" echo "::notice::oxlint.seatbelt.tsv unchanged; skipping auto-commit." else - echo "tightened=true" >> "$GITHUB_OUTPUT" + echo "updated=true" >> "$GITHUB_OUTPUT" fi - name: Setup git for OSBotify - if: steps.seatbelt_diff.outputs.tightened == 'true' + if: steps.seatbelt_diff.outputs.updated == 'true' uses: Expensify/GitHub-Actions/setupGitForOSBotify@main with: OP_VAULT: ${{ vars.OP_VAULT }} OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} SETUP_AS_APP: false - - name: Auto-commit tightened oxlint-seatbelt baseline - if: steps.seatbelt_diff.outputs.tightened == 'true' + - name: Auto-commit updated oxlint-seatbelt baseline + if: steps.seatbelt_diff.outputs.updated == 'true' continue-on-error: true run: | git add config/oxlint/oxlint.seatbelt.tsv - git commit -m "Auto-tighten oxlint-seatbelt baseline" + git commit -m "Auto-update oxlint-seatbelt baseline" git push origin main From c495bc3265b2c9d9e661fc9c86c9682a19a97622 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Fri, 23 Oct 2026 11:24:10 +0200 Subject: [PATCH 16/37] Report an oxlint baseline change only after checking the file changed --- .github/workflows/oxlint.yml | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/.github/workflows/oxlint.yml b/.github/workflows/oxlint.yml index 6c152d825dab..96123adccd53 100644 --- a/.github/workflows/oxlint.yml +++ b/.github/workflows/oxlint.yml @@ -38,27 +38,26 @@ jobs: SEATBELT_INCREASE: ${{ github.event_name == 'push' && github.event.head_commit.author.name != 'OSBotify' && 'all' || '' }} run: npm run lint -- --linter=oxlint --shards=4 - - name: Report the Oxlint result without failing the job - run: | - if [[ "${{ steps.oxlint.outcome }}" == "success" && "${{ github.event_name }}" == "push" ]]; then - echo "::notice::Shadow mode: new oxlint findings from this merge were added to the baseline instead of reported, so later PRs only see findings they introduce. ESLint is still the blocking check." - elif [[ "${{ steps.oxlint.outcome }}" == "success" ]]; then - echo "::notice::Oxlint found no errors above its baseline." - else - echo "::warning::Oxlint reported errors above its baseline. Non-blocking during the migration; see the step log." - fi - - name: Check for an updated oxlint-seatbelt baseline id: seatbelt_diff if: github.event_name == 'push' run: | if git diff --quiet config/oxlint/oxlint.seatbelt.tsv; then echo "updated=false" >> "$GITHUB_OUTPUT" - echo "::notice::oxlint.seatbelt.tsv unchanged; skipping auto-commit." else echo "updated=true" >> "$GITHUB_OUTPUT" fi + - name: Report the Oxlint result without failing the job + run: | + if [[ "${{ steps.oxlint.outcome }}" != "success" ]]; then + echo "::warning::Oxlint found errors this PR adds (see the step log). This check is temporarily non-blocking while oxlint runs next to ESLint." + elif [[ "${{ steps.seatbelt_diff.outputs.updated }}" == "true" ]]; then + echo "::notice::This merge changed the list of known oxlint errors (config/oxlint/oxlint.seatbelt.tsv). OSBotify commits the update. Temporary while oxlint runs next to ESLint." + else + echo "::notice::Oxlint found no new errors." + fi + - name: Setup git for OSBotify if: steps.seatbelt_diff.outputs.updated == 'true' uses: Expensify/GitHub-Actions/setupGitForOSBotify@main From f8fb9a0052970549e4e556c0ec27f7bab5f1c7a3 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Fri, 23 Oct 2026 11:34:25 +0200 Subject: [PATCH 17/37] Lint oxlint.config.mts with both linters and disable naming-convention in the file instead of ignoring it --- config/eslint/eslint.config.mjs | 2 -- oxlint.config.mts | 3 ++- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/config/eslint/eslint.config.mjs b/config/eslint/eslint.config.mjs index b145652ff4e1..6696ee50f090 100644 --- a/config/eslint/eslint.config.mjs +++ b/config/eslint/eslint.config.mjs @@ -824,8 +824,6 @@ const config = defineConfig([ globalIgnores([ '!**/.storybook', '!**/.github', - // Oxlint's config: its quoted rule-name keys trip naming-convention hundreds of times. - 'oxlint.config.mts', '.github/actions/**/index.js', '**/*.config.js', '**/*.config.mjs', diff --git a/oxlint.config.mts b/oxlint.config.mts index 92a15a7a8695..c2132147e025 100644 --- a/oxlint.config.mts +++ b/oxlint.config.mts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/naming-convention -- the rule names in this file are quoted object keys */ +/* eslint-disable import/no-extraneous-dependencies -- oxlint is a devDependency; this file only runs while linting */ import {defineConfig} from 'oxlint'; // An oxlint override replaces a rule's options instead of merging them, so every block that sets @@ -126,7 +128,6 @@ export default defineConfig({ 'import/extensions': ['.js', '.mjs', '.jsx', '.ts', '.tsx', '.d.ts', '.cts', '.mts'], }, ignorePatterns: [ - 'oxlint.config.mts', '!**/.storybook', '!**/.github', '.github/actions/**/index.js', From a23cdabc9e7e9a9f9b0c6ab64519ba3278200c5b Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Fri, 23 Oct 2026 11:57:23 +0200 Subject: [PATCH 18/37] Explain in core-rules.mjs which hosted rules are JS only and where their scopes live --- config/oxlint/plugins/core-rules.mjs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/config/oxlint/plugins/core-rules.mjs b/config/oxlint/plugins/core-rules.mjs index 77f210d3e3e4..2796ad2816d1 100644 --- a/config/oxlint/plugins/core-rules.mjs +++ b/config/oxlint/plugins/core-rules.mjs @@ -1,7 +1,19 @@ -// ESLint core rules oxlint has no native version of (checked against oxlint 1.89.0). dot-notation and -// no-return-await do have typed natives (typescript/dot-notation, typescript/return-await), but those are -// off for JS files, so these copies cover JS. no-invalid-this is left out although ESLint enables it: it -// calls sourceCode.getJSDocComment, which oxlint's bridge throws on as unsupported. +/** + * ESLint core rules that oxlint 1.89.0 has no native version of. They run inside oxlint as a JS + * plugin under the `core/` prefix, using ESLint's own rule code, so each one behaves as it does + * under ESLint. + * + * This file only exposes the rules. Which files each one runs on is set in oxlint.config.mts, and + * the scopes there copy ESLint: + * + * - dot-notation, no-return-await, no-dupe-args: JS files only. On TS files ESLint turns these off + * and runs the typed @typescript-eslint versions instead, which oxlint has natively. + * - lines-between-class-members: TS files only, as in ESLint. + * - no-restricted-syntax, no-octal, no-octal-escape, no-undef-init, strict: all files, as in ESLint. + * + * no-invalid-this is left out even though ESLint enables it. It calls sourceCode.getJSDocComment, + * which oxlint's plugin bridge does not support. + */ import {builtinRules} from 'eslint/use-at-your-own-risk'; import {withEslintDirectiveIdsFor} from '../eslintDirectives.mjs'; From 6673787d11aff4e73bb8688621323004d64f732b Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Fri, 23 Oct 2026 12:21:49 +0200 Subject: [PATCH 19/37] Drop the parentheses after Onyx.connectWithoutView in the OnyxUtils lint message so the reviewer bot ignores it --- oxlint.config.mts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oxlint.config.mts b/oxlint.config.mts index c2132147e025..42c7f8fb53c0 100644 --- a/oxlint.config.mts +++ b/oxlint.config.mts @@ -35,7 +35,7 @@ const restrictedImportPaths = [ // core rule accepts allowTypeImports instead. name: 'react-native-onyx/dist/OnyxUtils', message: - 'OnyxUtils is not a sanctioned way to read Onyx data. Use useOnyx() from @hooks/useOnyx in render paths, or a short-lived Onyx.connectWithoutView() for non-render logic. Type-only imports are still allowed.', + 'OnyxUtils is not a sanctioned way to read Onyx data. Use useOnyx() from @hooks/useOnyx in render paths, or a short-lived Onyx.connectWithoutView for non-render logic. Type-only imports are still allowed.', allowTypeImports: true, }, {name: 'awesome-phonenumber', importNames: ['parsePhoneNumber'], message: "Please use '@libs/PhoneNumber' instead."}, From 602bf3774b72fa309c75169bba3aebb1ce90395d Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Fri, 23 Oct 2026 13:36:15 +0200 Subject: [PATCH 20/37] Rename withEslintDirectiveIds to withEslintDirectiveIDs --- config/oxlint/eslintDirectives.mjs | 8 ++++---- config/oxlint/plugins/core-rules.mjs | 4 ++-- config/oxlint/plugins/hosted-rules.mjs | 10 +++++----- config/oxlint/plugins/rc-rules.mjs | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/config/oxlint/eslintDirectives.mjs b/config/oxlint/eslintDirectives.mjs index 1f8047da5852..4b1c2725f259 100644 --- a/config/oxlint/eslintDirectives.mjs +++ b/config/oxlint/eslintDirectives.mjs @@ -93,7 +93,7 @@ function reportPosition(args) { return point ? {line: point.line, column: typeof point.column === 'number' ? point.column : 0} : null; } -function withEslintDirectiveIds(rule, eslintRuleId) { +function withEslintDirectiveIDs(rule, eslintRuleId) { return { ...rule, create(context) { @@ -118,8 +118,8 @@ function withEslintDirectiveIds(rule, eslintRuleId) { }; } -function withEslintDirectiveIdsFor(rules, toEslintId) { - return Object.fromEntries(Object.entries(rules).map(([name, rule]) => [name, withEslintDirectiveIds(rule, toEslintId(name))])); +function withEslintDirectiveIDsFor(rules, toEslintId) { + return Object.fromEntries(Object.entries(rules).map(([name, rule]) => [name, withEslintDirectiveIDs(rule, toEslintId(name))])); } -export {withEslintDirectiveIds, withEslintDirectiveIdsFor}; +export {withEslintDirectiveIDs, withEslintDirectiveIDsFor}; diff --git a/config/oxlint/plugins/core-rules.mjs b/config/oxlint/plugins/core-rules.mjs index 2796ad2816d1..3dbd4f058e4d 100644 --- a/config/oxlint/plugins/core-rules.mjs +++ b/config/oxlint/plugins/core-rules.mjs @@ -16,7 +16,7 @@ */ import {builtinRules} from 'eslint/use-at-your-own-risk'; -import {withEslintDirectiveIdsFor} from '../eslintDirectives.mjs'; +import {withEslintDirectiveIDsFor} from '../eslintDirectives.mjs'; const NAMES = ['no-restricted-syntax', 'lines-between-class-members', 'no-octal', 'no-octal-escape', 'no-undef-init', 'strict', 'dot-notation', 'no-dupe-args', 'no-return-await']; @@ -25,7 +25,7 @@ const plugin = { name: 'core', version: '0.0.1', }, - rules: withEslintDirectiveIdsFor(Object.fromEntries(NAMES.map((name) => [name, builtinRules.get(name)])), (name) => name), + rules: withEslintDirectiveIDsFor(Object.fromEntries(NAMES.map((name) => [name, builtinRules.get(name)])), (name) => name), }; export default plugin; diff --git a/config/oxlint/plugins/hosted-rules.mjs b/config/oxlint/plugins/hosted-rules.mjs index 3b1239b770e0..0b467ff8aeb9 100644 --- a/config/oxlint/plugins/hosted-rules.mjs +++ b/config/oxlint/plugins/hosted-rules.mjs @@ -3,7 +3,7 @@ import path from 'node:path'; import {fileURLToPath} from 'node:url'; import {EXHAUSTIVE_DEPS_USECALLBACK_USEMEMO_PATTERN} from '../../reactCompiler/suppressedRules.mjs'; -import {withEslintDirectiveIds, withEslintDirectiveIdsFor} from '../eslintDirectives.mjs'; +import {withEslintDirectiveIDs, withEslintDirectiveIDsFor} from '../eslintDirectives.mjs'; import {create as noDeprecatedSelfClosingJsxCreate, meta as noDeprecatedSelfClosingJsxMeta} from '../noDeprecatedSelfClosingJsx.mjs'; import {withFullGating, withMessageGating} from '../reactCompilerGate.mjs'; import {hostedRuleNames} from '../ruleNames.mjs'; @@ -68,7 +68,7 @@ function withStubbedParserServices(rule) { } function hostRules(rules, eslintPrefix, names) { - return withEslintDirectiveIdsFor(Object.fromEntries(names.map((name) => [name, rules[name]])), (name) => `${eslintPrefix}/${name}`); + return withEslintDirectiveIDsFor(Object.fromEntries(names.map((name) => [name, rules[name]])), (name) => `${eslintPrefix}/${name}`); } const SEPARATELY_GATED = new Set(['jsx-no-constructed-context-values', 'exhaustive-deps']); @@ -88,9 +88,9 @@ const plugin = { ...hostRules(jsdoc, 'jsdoc', plainlyHosted('jsdoc')), ...hostRules(storybook, 'storybook', plainlyHosted('storybook')), ...hostRules({'naming-convention': withStubbedParserServices(typescriptEslint['naming-convention'])}, '@typescript-eslint', plainlyHosted('@typescript-eslint')), - 'jsx-no-constructed-context-values': withFullGating(withEslintDirectiveIds(react['jsx-no-constructed-context-values'], 'react/jsx-no-constructed-context-values')), - 'no-deprecated-self-closing-jsx': withEslintDirectiveIds({create: noDeprecatedSelfClosingJsxCreate, meta: noDeprecatedSelfClosingJsxMeta}, '@typescript-eslint/no-deprecated'), - 'exhaustive-deps': withMessageGating(withEslintDirectiveIds(reactHooks['exhaustive-deps'], 'react-hooks/exhaustive-deps'), EXHAUSTIVE_DEPS_USECALLBACK_USEMEMO_PATTERN), + 'jsx-no-constructed-context-values': withFullGating(withEslintDirectiveIDs(react['jsx-no-constructed-context-values'], 'react/jsx-no-constructed-context-values')), + 'no-deprecated-self-closing-jsx': withEslintDirectiveIDs({create: noDeprecatedSelfClosingJsxCreate, meta: noDeprecatedSelfClosingJsxMeta}, '@typescript-eslint/no-deprecated'), + 'exhaustive-deps': withMessageGating(withEslintDirectiveIDs(reactHooks['exhaustive-deps'], 'react-hooks/exhaustive-deps'), EXHAUSTIVE_DEPS_USECALLBACK_USEMEMO_PATTERN), }, }; diff --git a/config/oxlint/plugins/rc-rules.mjs b/config/oxlint/plugins/rc-rules.mjs index 48a8801f3bb0..570999d49f18 100644 --- a/config/oxlint/plugins/rc-rules.mjs +++ b/config/oxlint/plugins/rc-rules.mjs @@ -1,4 +1,4 @@ -import {withEslintDirectiveIds} from '../eslintDirectives.mjs'; +import {withEslintDirectiveIDs} from '../eslintDirectives.mjs'; import {RULE_BY_CATEGORY, reactCompilerDiagnostics} from '../reactCompilerRust.mjs'; function buildRule(ruleName) { @@ -28,7 +28,7 @@ function buildRule(ruleName) { }; } -const rules = Object.fromEntries(Object.values(RULE_BY_CATEGORY).map((ruleName) => [ruleName, withEslintDirectiveIds(buildRule(ruleName), `react-hooks/${ruleName}`)])); +const rules = Object.fromEntries(Object.values(RULE_BY_CATEGORY).map((ruleName) => [ruleName, withEslintDirectiveIDs(buildRule(ruleName), `react-hooks/${ruleName}`)])); const plugin = { meta: { From a92fc5905ad6553eaca46b9cf467c73b6bb124a1 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Fri, 23 Oct 2026 13:36:22 +0200 Subject: [PATCH 21/37] Reword the OnyxUtils lint message so the reviewer bot does not match it --- oxlint.config.mts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oxlint.config.mts b/oxlint.config.mts index 42c7f8fb53c0..22dc0b90cda1 100644 --- a/oxlint.config.mts +++ b/oxlint.config.mts @@ -35,7 +35,7 @@ const restrictedImportPaths = [ // core rule accepts allowTypeImports instead. name: 'react-native-onyx/dist/OnyxUtils', message: - 'OnyxUtils is not a sanctioned way to read Onyx data. Use useOnyx() from @hooks/useOnyx in render paths, or a short-lived Onyx.connectWithoutView for non-render logic. Type-only imports are still allowed.', + 'OnyxUtils is not a sanctioned way to read Onyx data. Use useOnyx() from @hooks/useOnyx in render paths, or a short-lived Onyx connection without a view for non-render logic. Type-only imports are still allowed.', allowTypeImports: true, }, {name: 'awesome-phonenumber', importNames: ['parsePhoneNumber'], message: "Please use '@libs/PhoneNumber' instead."}, From 6053df88f29d90fe57880970956db30006e0fa8f Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Fri, 23 Oct 2026 13:36:22 +0200 Subject: [PATCH 22/37] Say ESLint also replaces the restricted globals list for GitHub Actions files --- oxlint.config.mts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oxlint.config.mts b/oxlint.config.mts index 22dc0b90cda1..b23d22db8704 100644 --- a/oxlint.config.mts +++ b/oxlint.config.mts @@ -998,7 +998,7 @@ export default defineConfig({ }, }, { - // This replaces the root's list, so the airbnb browser globals are left out on purpose. + // Like ESLint's block, this replaces the root's list, so the airbnb browser globals are not banned here. files: ['.github/actions/**/*.ts', '.github/libs/**/*.ts'], rules: { 'no-restricted-globals': [ From bff2d27aebb8d4e7321fcb9844244f58e94e15c2 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Fri, 23 Oct 2026 13:36:23 +0200 Subject: [PATCH 23/37] Shorten the seatbelt comment in the oxlint workflow --- .github/workflows/oxlint.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/oxlint.yml b/.github/workflows/oxlint.yml index 96123adccd53..2de009114c09 100644 --- a/.github/workflows/oxlint.yml +++ b/.github/workflows/oxlint.yml @@ -29,11 +29,8 @@ jobs: id: oxlint continue-on-error: true # oxlint runs JS plugins on one thread (oxc#26621), so OxlintLinter splits them into 4 shards on this 8 vCPU runner. - # While oxlint is a non-blocking shadow job, the post-merge run on main absorbs new findings into the - # baseline as well as removing fixed ones, so later PRs are only warned about findings they add. - # Runs started by OSBotify's own baseline commit only tighten, so a finding that flickers between - # runs cannot make every run commit again. Remove this when oxlint becomes the blocking linter: - # from then on main only tightens, like ESLint. + # While oxlint is a non-blocking check, the post-merge run both adds and removes entries from the oxlint.seatbelt.tsv. + # Remove this once oxlint becomes the blocking linter. env: SEATBELT_INCREASE: ${{ github.event_name == 'push' && github.event.head_commit.author.name != 'OSBotify' && 'all' || '' }} run: npm run lint -- --linter=oxlint --shards=4 From 4e196492b159597c428f2a993a6bd75babeb7bd6 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Fri, 23 Oct 2026 13:36:34 +0200 Subject: [PATCH 24/37] Add a header comment to each oxlint config file --- config/oxlint/eslintDirectives.mjs | 8 ++++++-- config/oxlint/noDeprecatedSelfClosingJsx.mjs | 10 ++++++---- config/oxlint/plugins/expensify-rules.mjs | 5 +++++ config/oxlint/plugins/hosted-rules.mjs | 6 ++++++ config/oxlint/plugins/rc-rules.mjs | 4 ++++ config/oxlint/preferLocaleCompareFromContext.mjs | 9 ++++++--- config/oxlint/reactCompilerGate.mjs | 7 +++++-- config/oxlint/reactCompilerRust.mjs | 5 +++++ config/oxlint/ruleNames.mjs | 6 +++++- config/reactCompiler/suppressedRules.mjs | 6 +++++- oxlint.config.mts | 6 ++++++ 11 files changed, 59 insertions(+), 13 deletions(-) diff --git a/config/oxlint/eslintDirectives.mjs b/config/oxlint/eslintDirectives.mjs index 4b1c2725f259..f068b60d54fd 100644 --- a/config/oxlint/eslintDirectives.mjs +++ b/config/oxlint/eslintDirectives.mjs @@ -1,5 +1,9 @@ -// An `eslint-disable-next-line @typescript-eslint/naming-convention` does not suppress -// `hosted/naming-convention`, so this drops the reports ESLint would have suppressed under its own id. +/** + * oxlint runs some ESLint rules as JS plugins under a new name. For example, + * `@typescript-eslint/naming-convention` becomes `hosted/naming-convention`. oxlint doesn't know the + * two are the same rule, so an existing `eslint-disable-next-line @typescript-eslint/naming-convention` + * comment wouldn't hide its report. The wrappers here read those comments and drop what ESLint would hide. + */ const JUSTIFICATION = /\s-{2,}\s/; const KINDS = ['eslint-disable-next-line', 'eslint-disable-line', 'eslint-disable', 'eslint-enable']; diff --git a/config/oxlint/noDeprecatedSelfClosingJsx.mjs b/config/oxlint/noDeprecatedSelfClosingJsx.mjs index 7479ae40fb85..422e65dee6bc 100644 --- a/config/oxlint/noDeprecatedSelfClosingJsx.mjs +++ b/config/oxlint/noDeprecatedSelfClosingJsx.mjs @@ -1,7 +1,9 @@ -// tsgolint's no-deprecated skips the signature check on self-closing JSX (``), so a component -// deprecated only through its signature, like `MenuItem` wrapped in `Object.assign`, goes unreported. -// jsPlugins get no types, so the options list the components. Delete this rule once tsgolint reports -// these itself, or the findings will double. +/** + * Flags `` and other self-closing tags of components that are deprecated through their + * call signature. tsgolint's no-deprecated skips the signature check on self-closing JSX, so it misses + * these. JS plugins don't get type info, so the components are listed by hand in the rule options in + * oxlint.config.mts. Delete this rule once tsgolint catches these itself, or every finding shows up twice. + */ import fs from 'node:fs'; import path from 'node:path'; diff --git a/config/oxlint/plugins/expensify-rules.mjs b/config/oxlint/plugins/expensify-rules.mjs index b8ba6a97145b..2611ab96d110 100644 --- a/config/oxlint/plugins/expensify-rules.mjs +++ b/config/oxlint/plugins/expensify-rules.mjs @@ -1,3 +1,8 @@ +/** + * Loads every rule from eslint-plugin-expensify and eslint-plugin-local-rules into oxlint, under the + * `rulesdir` name ESLint uses. Two rules change: no-inline-useOnyx-selector stays quiet in files React + * Compiler memoizes, and prefer-locale-compare-from-context is swapped for a copy that needs no type info. + */ import fs from 'node:fs'; import {createRequire} from 'node:module'; import path from 'node:path'; diff --git a/config/oxlint/plugins/hosted-rules.mjs b/config/oxlint/plugins/hosted-rules.mjs index 0b467ff8aeb9..a0eab57a7c88 100644 --- a/config/oxlint/plugins/hosted-rules.mjs +++ b/config/oxlint/plugins/hosted-rules.mjs @@ -1,3 +1,9 @@ +/** + * Runs rules from ESLint plugins (react, import, jsdoc, storybook, react-hooks, typescript-eslint) + * inside oxlint, using the plugins' own code. Each one is here because oxlint has no native version or + * its native one reports differently. oxlint reserves most of these plugin names for its own ports, + * so all of them load under `hosted`. + */ import {createRequire} from 'node:module'; import path from 'node:path'; import {fileURLToPath} from 'node:url'; diff --git a/config/oxlint/plugins/rc-rules.mjs b/config/oxlint/plugins/rc-rules.mjs index 570999d49f18..ad299f9a4e3a 100644 --- a/config/oxlint/plugins/rc-rules.mjs +++ b/config/oxlint/plugins/rc-rules.mjs @@ -1,3 +1,7 @@ +/** + * The React Compiler rules from eslint-plugin-react-hooks (react-hooks/refs, react-hooks/purity, ...), + * rebuilt for oxlint under the `rc` name. They all share one compile per file, done in reactCompilerRust.mjs. + */ import {withEslintDirectiveIDs} from '../eslintDirectives.mjs'; import {RULE_BY_CATEGORY, reactCompilerDiagnostics} from '../reactCompilerRust.mjs'; diff --git a/config/oxlint/preferLocaleCompareFromContext.mjs b/config/oxlint/preferLocaleCompareFromContext.mjs index fe2f2f4a02df..21b21e570ab7 100644 --- a/config/oxlint/preferLocaleCompareFromContext.mjs +++ b/config/oxlint/preferLocaleCompareFromContext.mjs @@ -1,6 +1,9 @@ -// oxlint's JS plugins get no type information, so this drops the `isString(getTypeAtLocation(...))` -// receiver check of ESLint's rulesdir/prefer-locale-compare-from-context. Only String.prototype has -// `localeCompare`; an object defining its own is the one divergence. +/** + * oxlint version of rulesdir/prefer-locale-compare-from-context. The ESLint rule uses type info to check + * that `localeCompare` is called on a string. oxlint's JS plugins don't get type info, so this copy + * skips that check. Only strings have `localeCompare`, so the one difference is an object with its own + * `localeCompare`, which this copy also reports. + */ import {createRequire} from 'node:module'; import path from 'node:path'; import {fileURLToPath} from 'node:url'; diff --git a/config/oxlint/reactCompilerGate.mjs b/config/oxlint/reactCompilerGate.mjs index 5443a5f70fc3..fe4ad731dc19 100644 --- a/config/oxlint/reactCompilerGate.mjs +++ b/config/oxlint/reactCompilerGate.mjs @@ -1,5 +1,8 @@ -// oxlint has no processors and cannot filter a native Rust rule's diagnostics, so this replicates -// ESLint's react-compiler-compat processor by wrapping `context.report` in a JS plugin. +/** + * In files React Compiler already memoizes, lint shouldn't tell people to add useCallback or useMemo. + * For ESLint, scripts/lint's ReactCompilerFilter drops those reports. oxlint has no processors and + * can't filter its native rules, so here the JS plugin rule itself is wrapped and drops them. + */ import path from 'node:path'; import {didBothCompilersMemoizeFile} from '../reactCompiler/checkBoth.mjs'; diff --git a/config/oxlint/reactCompilerRust.mjs b/config/oxlint/reactCompilerRust.mjs index 212a57bce363..3988ced96252 100644 --- a/config/oxlint/reactCompilerRust.mjs +++ b/config/oxlint/reactCompilerRust.mjs @@ -1,3 +1,8 @@ +/** + * Runs oxc's Rust port of React Compiler on a file and turns what it finds into the same react-hooks/* + * reports eslint-plugin-react-hooks gives. plugins/rc-rules.mjs reports them. We use the Rust port + * because the JavaScript compiler in eslint-plugin-react-hooks would take up most of the oxlint run. + */ import path from 'node:path'; import {transformSync} from 'oxc-transform-react'; diff --git a/config/oxlint/ruleNames.mjs b/config/oxlint/ruleNames.mjs index 5f012b8cab12..8dd7f9fd324c 100644 --- a/config/oxlint/ruleNames.mjs +++ b/config/oxlint/ruleNames.mjs @@ -1,4 +1,8 @@ -// Plain `.mjs`: oxlint's JS plugin runtime cannot load TypeScript. +/** + * oxlint and ESLint name some rules differently. This maps between the two, so oxlint findings show up + * under ESLint's rule IDs and hosted-rules.mjs knows which ESLint plugin each hosted rule comes from. + * It's plain `.mjs` because oxlint's JS plugins can't load TypeScript. + */ const OXLINT_RULE_RENAMES = { 'no-object-constructor': 'no-new-object', 'no-new-native-nonconstructor': 'no-new-symbol', diff --git a/config/reactCompiler/suppressedRules.mjs b/config/reactCompiler/suppressedRules.mjs index 46d77c325c64..3f15e9dd6e6f 100644 --- a/config/reactCompiler/suppressedRules.mjs +++ b/config/reactCompiler/suppressedRules.mjs @@ -1,4 +1,8 @@ -// Plain `.mjs`: oxlint's JS plugin runtime cannot load TypeScript. +/** + * Lint reports that tell you to memoize by hand. Manual memoization is banned in files React Compiler + * already memoizes, so ReactCompilerFilter and the oxlint plugins drop these reports in those files. + * It's plain `.mjs` because oxlint's JS plugins can't load TypeScript. + */ const RULES_SUPPRESSED_BY_REACT_COMPILER = new Set(['react/jsx-no-constructed-context-values', 'rulesdir/no-inline-useOnyx-selector']); const EXHAUSTIVE_DEPS_USECALLBACK_USEMEMO_PATTERN = /\buseCallback\(\) Hook\b|\buseMemo\(\) Hook\b/; diff --git a/oxlint.config.mts b/oxlint.config.mts index b23d22db8704..3447b276936b 100644 --- a/oxlint.config.mts +++ b/oxlint.config.mts @@ -1,3 +1,9 @@ +/** + * oxlint runs next to ESLint for now (`npm run lint -- --linter=oxlint` and the non-blocking + * oxlint.yml workflow), so this file copies config/eslint/eslint.config.mjs rule by rule. + * If you change a rule there, change it here too. ESLint rules oxlint can't run natively come from + * the JS plugins in config/oxlint/plugins. + */ /* eslint-disable @typescript-eslint/naming-convention -- the rule names in this file are quoted object keys */ /* eslint-disable import/no-extraneous-dependencies -- oxlint is a devDependency; this file only runs while linting */ import {defineConfig} from 'oxlint'; From 04f1cf40ec381ef56dc3e20a668c3095c9747fbf Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Fri, 23 Oct 2026 13:36:40 +0200 Subject: [PATCH 25/37] Add JSDoc to the oxlint config functions --- config/oxlint/eslintDirectives.mjs | 51 ++++++++++++++++++- config/oxlint/noDeprecatedSelfClosingJsx.mjs | 34 +++++++++++++ config/oxlint/plugins/hosted-rules.mjs | 25 +++++++++ config/oxlint/plugins/rc-rules.mjs | 6 +++ .../oxlint/preferLocaleCompareFromContext.mjs | 12 ++++- config/oxlint/reactCompilerGate.mjs | 37 ++++++++++++-- config/oxlint/reactCompilerRust.mjs | 46 +++++++++++++++-- config/oxlint/ruleNames.mjs | 13 +++++ 8 files changed, 214 insertions(+), 10 deletions(-) diff --git a/config/oxlint/eslintDirectives.mjs b/config/oxlint/eslintDirectives.mjs index f068b60d54fd..6ac060840722 100644 --- a/config/oxlint/eslintDirectives.mjs +++ b/config/oxlint/eslintDirectives.mjs @@ -11,6 +11,12 @@ const ALL_RULES = ' all'; const cache = new Map(); +/** + * Turns a comment like `eslint-disable-next-line rule-a, rule-b -- reason` into its kind and the rules + * it names. A directive that names no rule applies to every rule. + * @param {import('estree').Comment} comment + * @returns {{kind: string, ruleIds: string[]} | null} null when the comment is not a directive + */ function parseComment(comment) { const text = String(comment.value ?? '').trim(); const kind = KINDS.find((candidate) => text === candidate || (text.startsWith(candidate) && /^\s/.test(text.slice(candidate.length)))); @@ -25,6 +31,13 @@ function parseComment(comment) { return {kind, ruleIds: ruleIds.length ? ruleIds : [ALL_RULES]}; } +/** + * Collects a file's disable comments in a form that is quick to check for each report. A + * `-line`/`-next-line` comment becomes the line it covers. `eslint-disable`/`eslint-enable` comments + * stay in source order, because the last one before a report decides. + * @param {import('estree').Comment[]} comments + * @returns {{lines: Map>, blocks: Array<{line: number, column: number, ruleId: string, disable: boolean}>}} + */ function parseDirectives(comments) { const lines = new Map(); const blocks = []; @@ -61,6 +74,12 @@ function parseDirectives(comments) { return {lines, blocks}; } +/** + * Every wrapped rule checks the same file's comments, so they are parsed once per file. + * @param {string | undefined} filename + * @param {import('estree').Comment[]} comments + * @returns {ReturnType} + */ function directivesFor(filename, comments) { const key = filename ?? ''; if (!cache.has(key)) { @@ -69,8 +88,15 @@ function directivesFor(filename, comments) { return cache.get(key); } -// As in ESLint, the last block directive before the report, by line and then column, that names the -// rule (or no rule) wins. +/** + * Tells whether ESLint would hide a report of `eslintRuleId` at `position`. As in ESLint, the last + * `eslint-disable`/`eslint-enable` before the report, by line and then column, that names the rule + * (or no rule) wins. + * @param {ReturnType} directives + * @param {string} eslintRuleId + * @param {{line: number, column: number} | null} position + * @returns {boolean} + */ function isSuppressed(directives, eslintRuleId, position) { if (position == null) { return false; @@ -90,6 +116,12 @@ function isSuppressed(directives, eslintRuleId, position) { return disabled; } +/** + * Finds the line and column a report points at, to check it against the disable comments. Rules pass + * either a descriptor with `loc` or `node`, or a node in the legacy `report(node, message)` form. + * @param {unknown[]} args + * @returns {{line: number, column: number} | null} + */ function reportPosition(args) { const first = args[0]; const candidates = [first?.loc?.start, first?.loc, first?.node?.loc?.start, first?.node?.loc]; @@ -97,6 +129,14 @@ function reportPosition(args) { return point ? {line: point.line, column: typeof point.column === 'number' ? point.column : 0} : null; } +/** + * Makes an oxlint JS plugin rule respect `eslint-disable` comments written for the ESLint rule it + * copies. Without it, `// eslint-disable-next-line react-hooks/exhaustive-deps` would not hide the + * oxlint copy's report, because oxlint calls that copy `hosted/exhaustive-deps`. + * @param {import('eslint').Rule.RuleModule} rule + * @param {string} eslintRuleId the ID ESLint reports this rule under, like `react-hooks/exhaustive-deps` + * @returns {import('eslint').Rule.RuleModule} + */ function withEslintDirectiveIDs(rule, eslintRuleId) { return { ...rule, @@ -122,6 +162,13 @@ function withEslintDirectiveIDs(rule, eslintRuleId) { }; } +/** + * withEslintDirectiveIDs for a whole plugin at once. `toEslintId` gives the ESLint name of each rule, + * like `naming-convention` to `@typescript-eslint/naming-convention`. + * @param {Record} rules + * @param {(name: string) => string} toEslintId maps an oxlint rule name to its ESLint ID + * @returns {Record} + */ function withEslintDirectiveIDsFor(rules, toEslintId) { return Object.fromEntries(Object.entries(rules).map(([name, rule]) => [name, withEslintDirectiveIDs(rule, toEslintId(name))])); } diff --git a/config/oxlint/noDeprecatedSelfClosingJsx.mjs b/config/oxlint/noDeprecatedSelfClosingJsx.mjs index 422e65dee6bc..49461f18a92a 100644 --- a/config/oxlint/noDeprecatedSelfClosingJsx.mjs +++ b/config/oxlint/noDeprecatedSelfClosingJsx.mjs @@ -11,6 +11,11 @@ import {fileURLToPath} from 'node:url'; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..'); +/** + * Reads tsconfig.json, which JSON.parse cannot read because it can have comments and trailing commas. + * @param {string} file + * @returns {any} + */ function readJsonc(file) { const text = fs .readFileSync(file, 'utf8') @@ -21,6 +26,11 @@ function readJsonc(file) { let aliasPrefixes; +/** + * Reads the import aliases (`@components/*`, `@libs/*`, ...) from tsconfig.json, so an import through + * an alias can be matched to the deprecated component's file. Read once, on first use. + * @returns {Array<[string, string]>} pairs of alias prefix and the absolute directory it points to + */ function getAliasPrefixes() { if (!aliasPrefixes) { const paths = readJsonc(path.join(repoRoot, 'tsconfig.json')).compilerOptions?.paths ?? {}; @@ -31,6 +41,13 @@ function getAliasPrefixes() { return aliasPrefixes; } +/** + * Turns an import path into the file it points to, so `@components/MenuItem` and `../MenuItem` both + * match the `module` set in the rule options. + * @param {string} source + * @param {string} filename the importing file + * @returns {string | null} null for package imports + */ function resolveModule(source, filename) { let resolved = null; if (source.startsWith('.')) { @@ -70,6 +87,11 @@ const meta = { ], }; +/** + * Tells which export an import takes: `import MenuItem from` takes `default`, `import {X as Y} from` takes `X`. + * @param {import('estree').ImportSpecifier | import('estree').ImportDefaultSpecifier | import('estree').ImportNamespaceSpecifier} specifier + * @returns {string | null} null for a namespace import + */ function importedName(specifier) { if (specifier.type === 'ImportDefaultSpecifier') { return 'default'; @@ -80,6 +102,12 @@ function importedName(specifier) { return null; } +/** + * Finds which identifiers a variable may hold, so a deprecated component renamed through + * `const Item = isNew ? MenuItem : OldItem` is still caught when `` is used. + * @param {import('estree').Expression | null | undefined} node + * @returns {string[]} + */ function initializerIdentifiers(node) { if (!node) { return []; @@ -100,6 +128,12 @@ function initializerIdentifiers(node) { } } +/** + * Collects the deprecated imports, their aliases and every `` tag while walking the file, and + * reports at the end, so it does not matter whether an alias is declared before or after its use. + * @param {import('eslint').Rule.RuleContext} context + * @returns {import('eslint').Rule.RuleListener} + */ function create(context) { const components = context.options[0]?.components ?? []; if (components.length === 0) { diff --git a/config/oxlint/plugins/hosted-rules.mjs b/config/oxlint/plugins/hosted-rules.mjs index a0eab57a7c88..f62e75940ebd 100644 --- a/config/oxlint/plugins/hosted-rules.mjs +++ b/config/oxlint/plugins/hosted-rules.mjs @@ -17,6 +17,11 @@ import {hostedRuleNames} from '../ruleNames.mjs'; const require = createRequire(import.meta.url); const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../..'); +/** + * Loads the rules of an installed ESLint plugin, so oxlint runs the same rule code as ESLint. + * @param {string} packageName an ESLint plugin package, or an absolute path to one + * @returns {Record} + */ function rulesOf(packageName) { let mod; try { @@ -51,6 +56,12 @@ const STUB_PARSER_SERVICES = { tsNodeToESTreeNodeMap: new Map(), }; +/** + * Lets typescript-eslint's naming-convention run in oxlint. The rule fails without TypeScript parser + * services, which oxlint's JS plugins do not have, so it gets STUB_PARSER_SERVICES instead. + * @param {import('eslint').Rule.RuleModule} rule + * @returns {import('eslint').Rule.RuleModule} + */ function withStubbedParserServices(rule) { return { ...rule, @@ -73,12 +84,26 @@ function withStubbedParserServices(rule) { }; } +/** + * Loads `names` from an ESLint plugin as hosted rules, and makes `eslint-disable` comments that use the + * ESLint name (`/`) apply to them. + * @param {Record} rules + * @param {string} eslintPrefix + * @param {string[]} names + * @returns {Record} + */ function hostRules(rules, eslintPrefix, names) { return withEslintDirectiveIDsFor(Object.fromEntries(names.map((name) => [name, rules[name]])), (name) => `${eslintPrefix}/${name}`); } const SEPARATELY_GATED = new Set(['jsx-no-constructed-context-values', 'exhaustive-deps']); +/** + * Lists the hosted rules from one ESLint plugin that React Compiler does not gate. The SEPARATELY_GATED + * ones are added one by one at the end of the file. + * @param {string} eslintPrefix + * @returns {string[]} + */ function plainlyHosted(eslintPrefix) { return hostedRuleNames(eslintPrefix).filter((name) => !SEPARATELY_GATED.has(name)); } diff --git a/config/oxlint/plugins/rc-rules.mjs b/config/oxlint/plugins/rc-rules.mjs index ad299f9a4e3a..0533a486e346 100644 --- a/config/oxlint/plugins/rc-rules.mjs +++ b/config/oxlint/plugins/rc-rules.mjs @@ -5,6 +5,12 @@ import {withEslintDirectiveIDs} from '../eslintDirectives.mjs'; import {RULE_BY_CATEGORY, reactCompilerDiagnostics} from '../reactCompilerRust.mjs'; +/** + * Builds the oxlint stand-in for one React Compiler rule of eslint-plugin-react-hooks, like + * `react-hooks/refs`. It reports only that rule's findings. The compile is shared (see reactCompilerDiagnostics). + * @param {string} ruleName a react-hooks rule name, like `refs` + * @returns {import('eslint').Rule.RuleModule} + */ function buildRule(ruleName) { return { meta: { diff --git a/config/oxlint/preferLocaleCompareFromContext.mjs b/config/oxlint/preferLocaleCompareFromContext.mjs index 21b21e570ab7..a8c9badfc1a7 100644 --- a/config/oxlint/preferLocaleCompareFromContext.mjs +++ b/config/oxlint/preferLocaleCompareFromContext.mjs @@ -12,7 +12,12 @@ const require = createRequire(import.meta.url); const pluginDir = path.dirname(fileURLToPath(import.meta.url)); const expensifyPlugin = path.resolve(pluginDir, '../../node_modules/eslint-config-expensify/eslint-plugin-expensify'); -/** `require` of these ESM modules returns the namespace, so unwrap `default`. */ +/** + * Loads a module from eslint-plugin-expensify, so this copy uses the same message as the original rule. + * `require` of these ESM modules returns the namespace, so unwrap `default`. + * @param {string} relative path inside eslint-plugin-expensify + * @returns {any} + */ function load(relative) { const module = require(path.join(expensifyPlugin, relative)); return module?.default ?? module; @@ -31,6 +36,11 @@ const meta = { schema: [], }; +/** + * Reports every `x.localeCompare(...)` outside tests. + * @param {import('eslint').Rule.RuleContext} context + * @returns {import('eslint').Rule.RuleListener} + */ function create(context) { return { CallExpression(node) { diff --git a/config/oxlint/reactCompilerGate.mjs b/config/oxlint/reactCompilerGate.mjs index fe4ad731dc19..249d667e5d8e 100644 --- a/config/oxlint/reactCompilerGate.mjs +++ b/config/oxlint/reactCompilerGate.mjs @@ -9,6 +9,13 @@ import {didBothCompilersMemoizeFile} from '../reactCompiler/checkBoth.mjs'; const memoizationCache = new Map(); +/** + * Tells whether React Compiler memoizes the whole file on both web and native. Compiling is slow, so + * each file is checked once. Tests and node_modules count as not memoized, as in ReactCompilerFilter. + * @param {string} filename + * @param {string} sourceText + * @returns {boolean} + */ function isFileMemoizedByBothCompilers(filename, sourceText) { if (!memoizationCache.has(filename)) { const normalized = filename.split(path.sep).join('/'); @@ -18,6 +25,13 @@ function isFileMemoizedByBothCompilers(filename, sourceText) { return memoizationCache.get(filename); } +/** + * Gets the final text of a report, so withMessageGating can match it. Rules pass either a `message` or + * a `messageId` into `meta.messages`, both with `{{placeholders}}`. + * @param {import('eslint').Rule.RuleModule} rule + * @param {import('eslint').Rule.ReportDescriptor | undefined} descriptor + * @returns {string} + */ function resolveMessage(rule, descriptor) { if (typeof descriptor?.message === 'string') { return descriptor.message.replaceAll(/\{\{\s*(\w+)\s*\}\}/g, (match, key) => descriptor.data?.[key] ?? match); @@ -29,9 +43,14 @@ function resolveMessage(rule, descriptor) { return template.replaceAll(/\{\{\s*(\w+)\s*\}\}/g, (match, key) => descriptor.data?.[key] ?? match); } -// Drops these rules' reports in files both React Compilers memoize, where the fix they suggest -// (useCallback/useMemo) is banned. Under scripts/lint this also leaves ReactCompilerFilter nothing to -// compile: the compile runs here, inside the parallel JS-plugin shards, instead of after them. +/** + * Drops the rule's reports in files both React Compilers memoize, where the fix they suggest + * (useCallback/useMemo) is banned. Doing it here also runs the compile inside the parallel JS-plugin + * shards, so under scripts/lint ReactCompilerFilter only sees these reports in files that are not memoized. + * @param {import('eslint').Rule.RuleModule} rule + * @param {(rule: import('eslint').Rule.RuleModule, descriptor: import('eslint').Rule.ReportDescriptor) => boolean} shouldGate picks the reports to drop + * @returns {import('eslint').Rule.RuleModule} + */ function withGating(rule, shouldGate) { return { ...rule, @@ -56,10 +75,22 @@ function withGating(rule, shouldGate) { }; } +/** + * For rules whose every report asks for manual memoization, like jsx-no-constructed-context-values. + * @param {import('eslint').Rule.RuleModule} rule + * @returns {import('eslint').Rule.RuleModule} + */ function withFullGating(rule) { return withGating(rule, () => true); } +/** + * For rules where only some reports ask for manual memoization. exhaustive-deps also reports missing + * dependencies, which still apply, so only reports whose message matches `pattern` are hidden. + * @param {import('eslint').Rule.RuleModule} rule + * @param {RegExp} pattern + * @returns {import('eslint').Rule.RuleModule} + */ function withMessageGating(rule, pattern) { return withGating(rule, (target, descriptor) => pattern.test(resolveMessage(target, descriptor))); } diff --git a/config/oxlint/reactCompilerRust.mjs b/config/oxlint/reactCompilerRust.mjs index 3988ced96252..267d9004f893 100644 --- a/config/oxlint/reactCompilerRust.mjs +++ b/config/oxlint/reactCompilerRust.mjs @@ -68,6 +68,11 @@ const CATEGORY_PATTERN = /react-compiler\(([^)]+)\)/; const cache = new Map(); +/** + * Picks the parser oxc-transform-react uses for the file. Plain JS files are parsed as JSX. + * @param {string} ext file extension without the dot + * @returns {'tsx' | 'ts' | 'jsx'} the `lang` oxc-transform-react parses the file as + */ function getLang(ext) { if (ext === 'tsx') { return 'tsx'; @@ -78,6 +83,11 @@ function getLang(ext) { return 'jsx'; } +/** + * Byte width of a character in UTF-8, used by buildOffsetToPoint to turn the compiler's byte offsets into columns. + * @param {number} code a code point + * @returns {number} how many UTF-8 bytes it takes + */ function utf8Length(code) { if (code < 0x80) { return 1; @@ -93,10 +103,14 @@ function utf8Length(code) { return 4; } -// `label.start`/`label.end` are UTF-8 byte offsets, while JavaScript strings index UTF-16 code units. -// Using them directly drifts in any non-ASCII file, anchoring diagnostics on the wrong line where -// `eslint-disable-next-line` no longer suppresses them. This maps a byte offset to the line and UTF-16 -// column oxlint's `loc` expects. +/** + * `label.start`/`label.end` are UTF-8 byte offsets, while JavaScript strings index UTF-16 code units. + * Using them directly drifts in any non-ASCII file, anchoring diagnostics on the wrong line where + * `eslint-disable-next-line` no longer suppresses them. This maps a byte offset to the line and UTF-16 + * column oxlint's `loc` expects. + * @param {string} source + * @returns {(offset: number) => {line: number, column: number}} + */ function buildOffsetToPoint(source) { const lineBytes = [0]; const lineUnits = [0]; @@ -111,6 +125,10 @@ function buildOffsetToPoint(source) { } } + /** + * @param {number} offset UTF-8 byte offset into `source` + * @returns {{line: number, column: number}} 1-based line and 0-based UTF-16 column + */ return function offsetToPoint(offset) { let low = 0; let high = lineBytes.length - 1; @@ -134,12 +152,25 @@ function buildOffsetToPoint(source) { }; } +/** + * Formats the message the way eslint-plugin-react-hooks does, so both linters report the same text. + * @param {string} category + * @param {{message?: string, helpMessage?: string}} error + * @returns {string} + */ function buildMessage(category, error) { const heading = COMPILATION_SKIPPED_CATEGORIES.has(category) ? 'Compilation Skipped' : 'Error'; const summary = `${heading}: ${error.message ?? 'Unknown compiler error'}`; return error.helpMessage ? `${summary}\n\n${error.helpMessage}.` : summary; } +/** + * Runs the compiler on one file and returns its findings as react-hooks rule reports. Throws on a + * category it does not know, so an oxc-transform-react bump that changes the categories fails the run. + * @param {string} filename + * @param {string} sourceText + * @returns {Array<{ruleName: string, loc: {start: {line: number, column: number}, end: {line: number, column: number}}, message: string}>} + */ function analyze(filename, sourceText) { let result; try { @@ -219,6 +250,13 @@ function analyze(filename, sourceText) { return diagnostics; } +/** + * Each rc/* rule (one per react-hooks rule) needs the same compiler output, so the compile runs once + * per file and the result is cached. + * @param {string} filename + * @param {string} sourceText + * @returns {ReturnType} + */ function reactCompilerDiagnostics(filename, sourceText) { if (!cache.has(filename)) { cache.set(filename, analyze(filename, sourceText)); diff --git a/config/oxlint/ruleNames.mjs b/config/oxlint/ruleNames.mjs index 8dd7f9fd324c..1985a56f24cd 100644 --- a/config/oxlint/ruleNames.mjs +++ b/config/oxlint/ruleNames.mjs @@ -59,10 +59,23 @@ const HOSTED_RULE_ESLINT_ID = { const OXLINT_DIAGNOSTIC_CODE = /^([\w@/.-]+)\((.+)\)$/; +/** + * Lists the hosted rules copied from one ESLint plugin, so hosted-rules.mjs knows which rules to load + * from that package. + * @param {string} eslintPrefix an ESLint plugin name, like `react` + * @returns {string[]} + */ function hostedRuleNames(eslintPrefix) { return Object.keys(HOSTED_RULE_ORIGIN).filter((name) => HOSTED_RULE_ORIGIN[name] === eslintPrefix); } +/** + * Converts oxlint's name for a finding, like `typescript(no-explicit-any)`, to ESLint's rule ID, like + * `@typescript-eslint/no-explicit-any`, so the lint output and the oxlint seatbelt baseline use the + * same rule names as ESLint. A code that does not look like `plugin(rule)` comes back unchanged. + * @param {string} code + * @returns {string} + */ function oxlintCodeToESLintRuleID(code) { const match = OXLINT_DIAGNOSTIC_CODE.exec(code); if (!match) { From c9c3bb70818a140bcc9f60f51a52584a2688eb4b Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Fri, 23 Oct 2026 13:45:20 +0200 Subject: [PATCH 26/37] Say what OxlintLinter does in its class comment --- scripts/lint/oxlint/OxlintLinter.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/lint/oxlint/OxlintLinter.ts b/scripts/lint/oxlint/OxlintLinter.ts index 47a21e652bb9..b7858feef962 100644 --- a/scripts/lint/oxlint/OxlintLinter.ts +++ b/scripts/lint/oxlint/OxlintLinter.ts @@ -345,6 +345,10 @@ function parseOxlintStdout(stdout: string, stderr: string, exitCode: number, pro } /** + * Runs oxlint for `npm run lint -- --linter=oxlint` and turns its JSON report into the same + * LinterResult ESLintLinter returns, so seatbelt and the formatters work for both. It can split the + * files into JS-plugin shards plus one type-aware process. + * * `--no-error-on-unmatched-pattern` is left off because it turns a mistyped path into a silent clean * pass; the empty-file-list case is detected explicitly instead. Oxlint has no `--cache`, so * `--no-cache` has no effect here. From 963b1629f462700a3d82f134e81667ef4ebef1b4 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Fri, 23 Oct 2026 13:52:24 +0200 Subject: [PATCH 27/37] Mirror main's narrowed prefer-nullish-coalescing in the oxlint config --- oxlint.config.mts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oxlint.config.mts b/oxlint.config.mts index 3447b276936b..8487a4d0c7a8 100644 --- a/oxlint.config.mts +++ b/oxlint.config.mts @@ -760,7 +760,7 @@ export default defineConfig({ 'typescript/non-nullable-type-assertion-style': 'error', 'typescript/prefer-for-of': 'error', 'typescript/prefer-function-type': 'error', - 'typescript/prefer-nullish-coalescing': ['error', {ignoreIfStatements: true, ignorePrimitives: {}, ignoreTernaryTests: true}], + 'typescript/prefer-nullish-coalescing': ['error', {ignoreIfStatements: true, ignorePrimitives: true, ignoreTernaryTests: true}], 'typescript/prefer-string-starts-ends-with': 'error', 'no-implied-eval': 'off', 'no-new-func': 'off', From 9d18084d82f8c3eb0ff88271e151c036f67fc8b5 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Fri, 23 Oct 2026 13:56:42 +0200 Subject: [PATCH 28/37] Re-baseline the oxlint seatbelt after merging main --- config/oxlint/oxlint.seatbelt.tsv | 53 ++++++++++--------------------- 1 file changed, 17 insertions(+), 36 deletions(-) diff --git a/config/oxlint/oxlint.seatbelt.tsv b/config/oxlint/oxlint.seatbelt.tsv index c250f4a36541..e768d05bc20d 100644 --- a/config/oxlint/oxlint.seatbelt.tsv +++ b/config/oxlint/oxlint.seatbelt.tsv @@ -22,7 +22,6 @@ "../../.github/actions/javascript/postOrReplaceComment/postOrReplaceComment.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../.github/actions/javascript/proposalPoliceComment/proposalPoliceComment.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../.github/actions/javascript/reviewerChecklist/reviewerChecklist.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 -"../../.github/actions/javascript/validateReassureOutput/validateReassureOutput.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../.github/libs/DeployChecklistUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../.github/libs/GithubUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 5 "../../.github/libs/sanitizeJSONStringValues.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 @@ -83,9 +82,8 @@ "../../src/components/AttachmentPicker/index.tsx" "react-hooks/refs" 1 "../../src/components/AttachmentPreview.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/Attachments/AttachmentCarousel/AttachmentCarouselView/index.tsx" "@typescript-eslint/no-deprecated/Gesture.Pan" 2 -"../../src/components/Attachments/AttachmentCarousel/AttachmentCarouselView/index.tsx" "@typescript-eslint/no-deprecated/GestureType" 3 +"../../src/components/Attachments/AttachmentCarousel/AttachmentCarouselView/index.tsx" "@typescript-eslint/no-deprecated/GestureType" 2 "../../src/components/Attachments/AttachmentCarousel/AttachmentCarouselView/index.tsx" "@typescript-eslint/no-deprecated/LegacyComposedGesture" 1 -"../../src/components/Attachments/AttachmentCarousel/AttachmentCarouselView/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../src/components/Attachments/AttachmentCarousel/AttachmentCarouselView/index.tsx" "react-hooks/refs" 3 "../../src/components/Attachments/AttachmentCarousel/index.tsx" "react-hooks/set-state-in-effect" 1 "../../src/components/Attachments/AttachmentCarousel/Pager/index.tsx" "@typescript-eslint/no-deprecated/Gesture.Native" 2 @@ -164,12 +162,9 @@ "../../src/components/DropZone/DropZoneWrapper.tsx" "react-hooks/refs" 1 "../../src/components/EditableCell/usePopoverEditState.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/EmbeddedDemo/index.native.tsx" "import/no-named-as-default" 1 -"../../src/components/EmojiPicker/EmojiPicker.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../src/components/EmojiPicker/EmojiPicker.tsx" "react-hooks/refs" 3 -"../../src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx" "react-hooks/preserve-manual-memoization" 1 "../../src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx" "react-hooks/refs" 2 -"../../src/components/EmojiPicker/EmojiPickerMenu/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../src/components/EmojiPicker/EmojiPickerMenu/index.tsx" "react-hooks/preserve-manual-memoization" 1 "../../src/components/EmojiPicker/EmojiPickerMenu/index.tsx" "react-hooks/refs" 2 "../../src/components/EmojiPicker/EmojiPickerMenu/useEmojiPickerMenu.ts" "react-hooks/set-state-in-effect" 2 @@ -248,6 +243,7 @@ "../../src/components/Lottie/index.tsx" "react-hooks/set-state-in-effect" 1 "../../src/components/MapView/MapView.tsx" "react-hooks/set-state-in-effect" 1 "../../src/components/MapView/MapView.web.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/components/MapView/MapViewImpl.web.tsx" "react-hooks/set-state-in-effect" 1 "../../src/components/MapView/ToggleDistanceUnitButton/index.android.tsx" "no-restricted-syntax" 1 "../../src/components/MenuItem/index.ts" "import/no-cycle" 4 "../../src/components/MenuItem/leaves/content/MenuItemFieldValueHTML.tsx" "import/no-cycle" 1 @@ -283,7 +279,6 @@ "../../src/components/MoneyRequestConfirmationListFooter/sections/ReceiptSection.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/MoneyRequestHeader.tsx" "no-restricted-imports" 1 "../../src/components/MoneyRequestReportView/MoneyRequestReportTransactionsNavigation.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 -"../../src/components/MoneyRequestReportView/MoneyRequestViewReportFields.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/MultifactorAuthentication/components/Modals/createCancelConfirmModal.tsx" "@typescript-eslint/no-deprecated/ConfirmModal" 1 "../../src/components/MultifactorAuthentication/components/OutcomeScreen/OutcomeScreenBase.tsx" "no-restricted-imports" 1 "../../src/components/MultifactorAuthentication/config/scenarios/ChangePIN.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 @@ -295,7 +290,7 @@ "../../src/components/MultiGestureCanvas/index.tsx" "@typescript-eslint/no-deprecated/Gesture.Race" 2 "../../src/components/MultiGestureCanvas/index.tsx" "@typescript-eslint/no-deprecated/Gesture.Simultaneous" 2 "../../src/components/MultiGestureCanvas/index.tsx" "@typescript-eslint/no-deprecated/GestureRef" 1 -"../../src/components/MultiGestureCanvas/index.tsx" "@typescript-eslint/no-deprecated/GestureType" 2 +"../../src/components/MultiGestureCanvas/index.tsx" "@typescript-eslint/no-deprecated/GestureType" 1 "../../src/components/MultiGestureCanvas/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/MultiGestureCanvas/index.tsx" "no-restricted-syntax" 1 "../../src/components/MultiGestureCanvas/index.tsx" "react-hooks/preserve-manual-memoization" 1 @@ -371,7 +366,6 @@ "../../src/components/ReportActionAvatars/index.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../src/components/ReportActionAvatars/useReportPreviewSenderID.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../src/components/ReportActionItem/ExportWithDropdownMenu.tsx" "react-hooks/preserve-manual-memoization" 3 -"../../src/components/ReportActionItem/MoneyReportView.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/ReportActionItem/MoneyRequestReceiptView.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../src/components/ReportActionItem/MoneyRequestReceiptView.tsx" "react-hooks/set-state-in-effect" 1 "../../src/components/ReportActionItem/MoneyRequestView.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 5 @@ -608,7 +602,6 @@ "../../src/hooks/useDebounceNonReactive.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/hooks/useDragAndDrop/types.ts" "@typescript-eslint/no-deprecated/React.MutableRefObject" 1 "../../src/hooks/useDynamicBackPath.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 -"../../src/hooks/useDynamicBackPath.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/hooks/useDynamicForwardPath.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../src/hooks/useExportedToFilterOptions.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/hooks/useFilterFormValues.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 @@ -649,7 +642,6 @@ "../../src/hooks/useReviewDuplicatesNavigation.tsx" "react-hooks/set-state-in-effect" 1 "../../src/hooks/useSearchBulkActions.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 "../../src/hooks/useSearchBulkActions.ts" "@typescript-eslint/no-unsafe-type-assertion" 9 -"../../src/hooks/useSidebarOrderedReports.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/hooks/useSidebarOrderedReports.tsx" "react-hooks/preserve-manual-memoization" 5 "../../src/hooks/useSidebarOrderedReports.tsx" "react-hooks/refs" 5 "../../src/hooks/useSidebarOrderedReports.tsx" "react-hooks/set-state-in-effect" 1 @@ -672,7 +664,6 @@ "../../src/libs/actions/App.ts" "import/no-cycle" 1 "../../src/libs/actions/App.ts" "no-restricted-syntax" 1 "../../src/libs/actions/applyOnyxUpdatesReliably.ts" "no-restricted-syntax" 1 -"../../src/libs/actions/BankAccounts.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../src/libs/actions/BankAccounts.ts" "import/no-cycle" 1 "../../src/libs/actions/BankAccounts.ts" "no-restricted-syntax" 2 "../../src/libs/actions/CanvasSize.ts" "no-restricted-syntax" 1 @@ -681,8 +672,6 @@ "../../src/libs/actions/Chronos.ts" "no-restricted-syntax" 1 "../../src/libs/actions/clearOnyxAndSeedFullReconnect.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/libs/actions/ClearReportActionErrors.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 -"../../src/libs/actions/CompanyCards.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 -"../../src/libs/actions/CompanyCards.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../src/libs/actions/CompanyCards.ts" "no-restricted-imports" 1 "../../src/libs/actions/CompanyCards.ts" "no-restricted-syntax" 4 "../../src/libs/actions/connections/index.ts" "import/no-cycle" 1 @@ -777,7 +766,6 @@ "../../src/libs/actions/PersonalDetails.ts" "no-restricted-syntax" 5 "../../src/libs/actions/Plaid.ts" "no-restricted-syntax" 1 "../../src/libs/actions/Policy/Category.ts" "no-restricted-syntax" 5 -"../../src/libs/actions/Policy/DistanceRate.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 "../../src/libs/actions/Policy/DistanceRate.ts" "no-restricted-syntax" 2 "../../src/libs/actions/Policy/Member.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../src/libs/actions/Policy/Member.ts" "no-restricted-imports" 1 @@ -906,7 +894,6 @@ "../../src/libs/focusComposerWithDelay/index.ts" "no-restricted-syntax" 1 "../../src/libs/focusComposerWithDelay/setTextInputSelection.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../src/libs/Formula.ts" "@typescript-eslint/no-deprecated/getReportTransactions" 1 -"../../src/libs/Fullstory/common.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../src/libs/getPlatform/index.ts" "no-restricted-syntax" 1 "../../src/libs/getPolicyFromSearchData.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../src/libs/getReturnValue.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 @@ -984,6 +971,7 @@ "../../src/libs/Navigation/helpers/dynamicRoutesUtils/getDynamicRouteAdaptedState.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/libs/Navigation/helpers/dynamicRoutesUtils/getDynamicRouteQueryParams.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/libs/Navigation/helpers/dynamicRoutesUtils/getPathWithoutDynamicSuffix.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Navigation/helpers/dynamicRoutesUtils/stripDynamicRouteSuffixFromPath.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/libs/Navigation/helpers/findFocusedRouteWithOnyxTabGuard.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../src/libs/Navigation/helpers/getAdaptedStateFromPath.ts" "@typescript-eslint/no-unnecessary-type-assertion" 3 "../../src/libs/Navigation/helpers/getAdaptedStateFromPath.ts" "@typescript-eslint/no-unsafe-type-assertion" 4 @@ -1010,6 +998,7 @@ "../../src/libs/Navigation/helpers/swapBackgroundTabForRHPTarget.ts" "@typescript-eslint/no-unsafe-type-assertion" 4 "../../src/libs/Navigation/helpers/tabNavigatorUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../src/libs/Navigation/helpers/useIsSidebarRouteActive.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Navigation/helpers/wideTabPreMount.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 "../../src/libs/Navigation/linkingConfig/config.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../src/libs/Navigation/linkingConfig/config.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../src/libs/Navigation/linkingConfig/RELATIONS/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 3 @@ -1086,7 +1075,7 @@ "../../src/libs/SearchQueryUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 48 "../../src/libs/SearchQueryUtils.ts" "import/no-cycle" 4 "../../src/libs/SearchQueryUtils.ts" "unicorn/prefer-string-replace-all" 1 -"../../src/libs/SearchUIUtils.ts" "@typescript-eslint/no-unnecessary-type-assertion" 4 +"../../src/libs/SearchUIUtils.ts" "@typescript-eslint/no-unnecessary-type-assertion" 3 "../../src/libs/SearchUIUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 71 "../../src/libs/SearchUIUtils.ts" "import/no-cycle" 1 "../../src/libs/SearchUIUtils.ts" "no-restricted-imports" 1 @@ -1098,7 +1087,6 @@ "../../src/libs/StatusBar/index.android.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/libs/StatusBar/index.web.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/libs/StatusBar/types.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 -"../../src/libs/StringUtils/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../src/libs/telemetry/forwardLogsToSentry.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/libs/telemetry/TelemetrySynchronizer.ts" "rulesdir/no-direct-personal-details-list" 1 "../../src/libs/TransactionPreviewUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 @@ -1119,8 +1107,6 @@ "../../src/libs/updateMultilineInputRange/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/libs/Url.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../src/libs/UserAvatarUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 3 -"../../src/libs/ValidationUtils.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 -"../../src/libs/ValidationUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../src/libs/Violations/ViolationsUtils.ts" "import/no-cycle" 4 "../../src/libs/Violations/ViolationsUtils.ts" "no-restricted-syntax" 1 "../../src/libs/Wallet/index.ios.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 @@ -1341,7 +1327,6 @@ "../../src/pages/LoadingPage.tsx" "no-restricted-imports" 1 "../../src/pages/LogInWithShortLivedAuthTokenPage.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../src/pages/LogOutPreviousUserPage.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 -"../../src/pages/media/AttachmentModalScreen/AttachmentModalBaseContent/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 "../../src/pages/media/AttachmentModalScreen/AttachmentModalBaseContent/index.tsx" "no-restricted-imports" 1 "../../src/pages/media/AttachmentModalScreen/AttachmentModalBaseContent/index.tsx" "react-hooks/set-state-in-effect" 3 "../../src/pages/media/AttachmentModalScreen/AttachmentModalContext.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 @@ -1928,7 +1913,6 @@ "../../src/pages/workspace/expensifyCard/DynamicWorkspaceExpensifyCardDetailsPage.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 "../../src/pages/workspace/expensifyCard/DynamicWorkspaceExpensifyCardDetailsPage.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 "../../src/pages/workspace/expensifyCard/DynamicWorkspaceExpensifyCardDetailsPage.tsx" "no-restricted-imports" 1 -"../../src/pages/workspace/expensifyCard/DynamicWorkspaceSettlementAccountPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 "../../src/pages/workspace/expensifyCard/DynamicWorkspaceSettlementAccountPage.tsx" "no-restricted-imports" 1 "../../src/pages/workspace/expensifyCard/issueNew/CardTypeStep.tsx" "@typescript-eslint/no-deprecated/MenuItem" 2 "../../src/pages/workspace/expensifyCard/issueNew/LimitTypeStep.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 @@ -2075,7 +2059,7 @@ "../../src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsCreatePage.tsx" "no-restricted-imports" 1 "../../src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx" "no-restricted-imports" 1 "../../src/pages/workspace/workflows/tabs/WorkflowsPaymentsTab.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 -"../../src/pages/workspace/workflows/tabs/WorkflowsPaymentsTab.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../src/pages/workspace/workflows/tabs/WorkflowsPaymentsTab.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 "../../src/pages/workspace/workflows/tabs/WorkflowsPaymentsTab.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx" "no-restricted-imports" 1 @@ -2089,7 +2073,6 @@ "../../src/pages/workspace/WorkspaceMembersPage.tsx" "no-restricted-imports" 1 "../../src/pages/workspace/WorkspaceMoreFeaturesPage/index.tsx" "no-restricted-imports" 1 "../../src/pages/workspace/WorkspaceNamePage.tsx" "no-restricted-imports" 1 -"../../src/pages/workspace/WorkspaceNewRoomPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 "../../src/pages/workspace/WorkspaceNewRoomPage.tsx" "no-restricted-imports" 1 "../../src/pages/workspace/WorkspaceNewRoomPage.tsx" "react-hooks/set-state-in-effect" 3 "../../src/pages/workspace/WorkspaceOverviewClientIDPage.tsx" "no-restricted-imports" 1 @@ -2273,8 +2256,6 @@ "../../tests/ui/components/MoneyReportHeaderFocusSelectionModeTest.tsx" "no-restricted-imports" 1 "../../tests/ui/components/MoneyReportHeaderSelectionModeTest.tsx" "no-restricted-imports" 1 "../../tests/ui/components/MoneyRequestHeaderSecondaryActionsTest.tsx" "@typescript-eslint/no-deprecated/originalMessage" 1 -"../../tests/ui/components/SelectionToolbarSubmitToPopoverTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 -"../../tests/ui/components/SelectionToolbarSubmitToPopoverTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../tests/ui/components/SettlementButtonTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 7 "../../tests/ui/ComposerImperativeRefTest.tsx" "@typescript-eslint/no-deprecated/keyCode" 1 "../../tests/ui/ComposerTest.tsx" "rulesdir/no-raw-typography" 1 @@ -2298,12 +2279,12 @@ "../../tests/ui/IssueNewCardConfirmValidateCodePageTest.tsx" "no-unsafe-optional-chaining" 1 "../../tests/ui/MoneyReportViewTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 "../../tests/ui/MoneyRequestReportActionsListRejectModalTest.tsx" "@typescript-eslint/no-deprecated/originalMessage" 2 -"../../tests/ui/MoneyRequestReportActionsListRejectModalTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 10 +"../../tests/ui/MoneyRequestReportActionsListRejectModalTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 11 "../../tests/ui/MoneyRequestReportPreview.test.tsx" "@typescript-eslint/no-deprecated/originalMessage" 5 "../../tests/ui/MoneyRequestReportPreview.test.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 4 "../../tests/ui/MoneyRequestReportPreview.test.tsx" "no-restricted-imports" 1 "../../tests/ui/MoneyRequestViewReceiptTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 -"../../tests/ui/MoneyRequestViewTest.tsx" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/ui/MoneyRequestViewTest.tsx" "@typescript-eslint/no-deprecated/originalMessage" 2 "../../tests/ui/MyTripsPageTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../tests/ui/NumericBigNumberPadTest.tsx" "no-inner-declarations" 1 "../../tests/ui/ProfilePageTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 5 @@ -2332,6 +2313,7 @@ "../../tests/ui/WorkspaceInviteMessageDEWApproverRowTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/ui/WorkspaceInviteMessageFastEditSaveTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/ui/WorkspaceInvoiceVBASectionTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/ui/WorkspaceMembersChangeWorkArrangementPageTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/ui/WorkspaceMoreFeaturesPageTest.tsx" "no-restricted-imports" 1 "../../tests/ui/WorkspacePageWithSectionsTest.tsx" "@typescript-eslint/no-unsafe-member-access" 1 "../../tests/ui/WorkspaceWorkflowsApprovalsCreatePageTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 @@ -2366,7 +2348,7 @@ "../../tests/unit/components/MoneyReportTransactionThreadContext.test.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 "../../tests/unit/components/reportDetails/ReportDetailsMenuItemsTest.tsx" "@typescript-eslint/no-deprecated/originalMessage" 1 "../../tests/unit/ComposerLocalTimeTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 -"../../tests/unit/ContextMenuActionsCopyMessageTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 6 +"../../tests/unit/ContextMenuActionsCopyMessageTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 7 "../../tests/unit/ContextMenuActionsDeleteTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 "../../tests/unit/CopyPolicySettingsConfirmTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/unit/CopyPolicySettingsConfirmTest.tsx" "react-hooks/immutability" 1 @@ -2376,7 +2358,7 @@ "../../tests/unit/CreateReportMenuItemTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/unit/DateUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/unit/DateUtilsTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 3 -"../../tests/unit/DistanceRateTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/DistanceRateTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 "../../tests/unit/DistanceRequestUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/unit/ExpenseDefaultRuleUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/unit/FlaggedExpensesTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 4 @@ -2405,8 +2387,6 @@ "../../tests/unit/hooks/useDeleteTransactionsInvoiceOfflineTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 4 "../../tests/unit/hooks/useDismissOnMoneyRequestReportRemoval.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 "../../tests/unit/hooks/useExpenseActionsInvoiceDeleteTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 -"../../tests/unit/hooks/useExpenseActionsSingleExpenseTableViewDeleteTest.tsx" "@typescript-eslint/no-deprecated/originalMessage" 1 -"../../tests/unit/hooks/useExpenseActionsSingleExpenseTableViewDeleteTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../tests/unit/hooks/useExpenseSubmission.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 6 "../../tests/unit/hooks/useFeedKeysWithAssignedCards.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 5 "../../tests/unit/hooks/useFilesValidation.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 4 @@ -2426,7 +2406,7 @@ "../../tests/unit/hooks/useSeedMyExpensesSearch.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/unit/hooks/useSelectedTransactionsActions.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 4 "../../tests/unit/hooks/useSelectionModeReportActions.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 -"../../tests/unit/hooks/useSingleExpenseReportView.test.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 +"../../tests/unit/hooks/useShouldAddOrReplaceReceipt.test.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 "../../tests/unit/hooks/useSplitEffectivePolicy.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/unit/hooks/useSplitParticipants.test.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/unit/hooks/useTimeSensitiveAddBankAccount.test.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 @@ -2444,6 +2424,7 @@ "../../tests/unit/LHNAvatarPipelineTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 7 "../../tests/unit/LhnSearchPreviewParityTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 25 "../../tests/unit/LhnSearchPreviewParityTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../tests/unit/libs/cameraCaptureTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../tests/unit/libs/receiptStorageTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/unit/libs/TransactionThreadNavigationUtilsTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 "../../tests/unit/libs/TransactionThreadNavigationUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 @@ -2461,6 +2442,7 @@ "../../tests/unit/Navigation/getTabStateWithFocusedTargetTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../tests/unit/Navigation/hasDifferentSplitScope.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 3 "../../tests/unit/Navigation/preMountBufferTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 8 +"../../tests/unit/Navigation/wideTabPreMountTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 4 "../../tests/unit/NextStepUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 6 "../../tests/unit/NumberFormatUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 "../../tests/unit/OnboardingFlowTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 @@ -2495,10 +2477,10 @@ "../../tests/unit/ReportActionsUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 38 "../../tests/unit/ReportAlternateTextParityTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 15 "../../tests/unit/ReportAlternateTextParityTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 -"../../tests/unit/ReportAlternateTextUtilsTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 67 +"../../tests/unit/ReportAlternateTextUtilsTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 68 "../../tests/unit/ReportAlternateTextUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 "../../tests/unit/reportAttributesTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 2 -"../../tests/unit/ReportNameUtilsTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 33 +"../../tests/unit/ReportNameUtilsTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 34 "../../tests/unit/ReportPrimaryActionUtilsTest.ts" "no-restricted-imports" 1 "../../tests/unit/ReportSecondaryActionUtilsTest.ts" "no-restricted-imports" 2 "../../tests/unit/ReportSelectorTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 6 @@ -2532,7 +2514,6 @@ "../../tests/unit/SearchAutocompleteListTest.tsx" "no-restricted-imports" 1 "../../tests/unit/SearchEditMultipleUtilsTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 "../../tests/unit/SequentialQueueTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 -"../../tests/unit/shouldShowPushNotificationTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 "../../tests/unit/showReportActionNotificationTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 3 "../../tests/unit/SidebarLinksTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/unit/SidebarLinksTest.tsx" "react/button-has-type" 1 From 1171139632481dbaa7b06df4b038e5962796d409 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Fri, 23 Oct 2026 14:17:01 +0200 Subject: [PATCH 29/37] Write the ESTree types inline in the oxlint JSDoc so knip finds no unlisted estree --- config/oxlint/eslintDirectives.mjs | 6 +++--- config/oxlint/noDeprecatedSelfClosingJsx.mjs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/config/oxlint/eslintDirectives.mjs b/config/oxlint/eslintDirectives.mjs index 6ac060840722..e0f5de6a59ac 100644 --- a/config/oxlint/eslintDirectives.mjs +++ b/config/oxlint/eslintDirectives.mjs @@ -14,7 +14,7 @@ const cache = new Map(); /** * Turns a comment like `eslint-disable-next-line rule-a, rule-b -- reason` into its kind and the rules * it names. A directive that names no rule applies to every rule. - * @param {import('estree').Comment} comment + * @param {{type: 'Line' | 'Block', value: string, loc?: {start: {line: number, column: number}, end: {line: number, column: number}}}} comment * @returns {{kind: string, ruleIds: string[]} | null} null when the comment is not a directive */ function parseComment(comment) { @@ -35,7 +35,7 @@ function parseComment(comment) { * Collects a file's disable comments in a form that is quick to check for each report. A * `-line`/`-next-line` comment becomes the line it covers. `eslint-disable`/`eslint-enable` comments * stay in source order, because the last one before a report decides. - * @param {import('estree').Comment[]} comments + * @param {Array[0]>} comments * @returns {{lines: Map>, blocks: Array<{line: number, column: number, ruleId: string, disable: boolean}>}} */ function parseDirectives(comments) { @@ -77,7 +77,7 @@ function parseDirectives(comments) { /** * Every wrapped rule checks the same file's comments, so they are parsed once per file. * @param {string | undefined} filename - * @param {import('estree').Comment[]} comments + * @param {Array[0]>} comments * @returns {ReturnType} */ function directivesFor(filename, comments) { diff --git a/config/oxlint/noDeprecatedSelfClosingJsx.mjs b/config/oxlint/noDeprecatedSelfClosingJsx.mjs index 49461f18a92a..cd1dd08bf5a1 100644 --- a/config/oxlint/noDeprecatedSelfClosingJsx.mjs +++ b/config/oxlint/noDeprecatedSelfClosingJsx.mjs @@ -89,7 +89,7 @@ const meta = { /** * Tells which export an import takes: `import MenuItem from` takes `default`, `import {X as Y} from` takes `X`. - * @param {import('estree').ImportSpecifier | import('estree').ImportDefaultSpecifier | import('estree').ImportNamespaceSpecifier} specifier + * @param {{type: 'ImportSpecifier' | 'ImportDefaultSpecifier' | 'ImportNamespaceSpecifier', imported?: {type: string, name?: string, value?: string}}} specifier * @returns {string | null} null for a namespace import */ function importedName(specifier) { @@ -105,7 +105,7 @@ function importedName(specifier) { /** * Finds which identifiers a variable may hold, so a deprecated component renamed through * `const Item = isNew ? MenuItem : OldItem` is still caught when `` is used. - * @param {import('estree').Expression | null | undefined} node + * @param {{type: string} | null | undefined} node the expression a variable is initialized with * @returns {string[]} */ function initializerIdentifiers(node) { From 26441b2c804a79effafbd4c7dcf98b7ec0053a3a Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Fri, 23 Oct 2026 14:17:02 +0200 Subject: [PATCH 30/37] Re-baseline the oxlint seatbelt after merging main --- config/oxlint/oxlint.seatbelt.tsv | 28 ++++------------------------ 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/config/oxlint/oxlint.seatbelt.tsv b/config/oxlint/oxlint.seatbelt.tsv index e768d05bc20d..f02ccd4c79af 100644 --- a/config/oxlint/oxlint.seatbelt.tsv +++ b/config/oxlint/oxlint.seatbelt.tsv @@ -81,16 +81,9 @@ "../../src/components/AttachmentPicker/index.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../src/components/AttachmentPicker/index.tsx" "react-hooks/refs" 1 "../../src/components/AttachmentPreview.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 -"../../src/components/Attachments/AttachmentCarousel/AttachmentCarouselView/index.tsx" "@typescript-eslint/no-deprecated/Gesture.Pan" 2 -"../../src/components/Attachments/AttachmentCarousel/AttachmentCarouselView/index.tsx" "@typescript-eslint/no-deprecated/GestureType" 2 -"../../src/components/Attachments/AttachmentCarousel/AttachmentCarouselView/index.tsx" "@typescript-eslint/no-deprecated/LegacyComposedGesture" 1 -"../../src/components/Attachments/AttachmentCarousel/AttachmentCarouselView/index.tsx" "react-hooks/refs" 3 "../../src/components/Attachments/AttachmentCarousel/index.tsx" "react-hooks/set-state-in-effect" 1 -"../../src/components/Attachments/AttachmentCarousel/Pager/index.tsx" "@typescript-eslint/no-deprecated/Gesture.Native" 2 "../../src/components/Attachments/AttachmentCarousel/Pager/index.tsx" "react-hooks/set-state-in-effect" 1 -"../../src/components/Attachments/AttachmentCarousel/Pager/types.ts" "@typescript-eslint/no-deprecated/GestureType" 2 "../../src/components/Attachments/AttachmentCarousel/useCarouselArrows.ts" "no-restricted-syntax" 1 -"../../src/components/Attachments/AttachmentView/AttachmentViewPdf/index.android.tsx" "@typescript-eslint/no-deprecated/Gesture.Pan" 2 "../../src/components/Attachments/AttachmentView/AttachmentViewPdf/index.tsx" "import/no-cycle" 1 "../../src/components/Attachments/AttachmentView/AttachmentViewVideo/index.tsx" "import/no-cycle" 1 "../../src/components/Attachments/AttachmentView/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 4 @@ -286,24 +279,10 @@ "../../src/components/MultifactorAuthentication/Context/stateReducer.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/MultifactorAuthentication/Context/usePromptContent.ts" "react-hooks/refs" 6 "../../src/components/MultifactorAuthentication/ValidateCodeResendButton.tsx" "no-restricted-syntax" 1 -"../../src/components/MultiGestureCanvas/index.tsx" "@typescript-eslint/no-deprecated/Gesture.Pan" 2 -"../../src/components/MultiGestureCanvas/index.tsx" "@typescript-eslint/no-deprecated/Gesture.Race" 2 -"../../src/components/MultiGestureCanvas/index.tsx" "@typescript-eslint/no-deprecated/Gesture.Simultaneous" 2 -"../../src/components/MultiGestureCanvas/index.tsx" "@typescript-eslint/no-deprecated/GestureRef" 1 -"../../src/components/MultiGestureCanvas/index.tsx" "@typescript-eslint/no-deprecated/GestureType" 1 -"../../src/components/MultiGestureCanvas/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/MultiGestureCanvas/index.tsx" "no-restricted-syntax" 1 -"../../src/components/MultiGestureCanvas/index.tsx" "react-hooks/preserve-manual-memoization" 1 -"../../src/components/MultiGestureCanvas/index.tsx" "react-hooks/refs" 2 -"../../src/components/MultiGestureCanvas/usePanGesture.ts" "@typescript-eslint/no-deprecated/Gesture.Pan" 2 -"../../src/components/MultiGestureCanvas/usePanGesture.ts" "@typescript-eslint/no-deprecated/LegacyPanGesture" 1 -"../../src/components/MultiGestureCanvas/usePanGesture.ts" "no-restricted-syntax" 1 -"../../src/components/MultiGestureCanvas/usePinchGesture.ts" "@typescript-eslint/no-deprecated/Gesture.Pinch" 2 -"../../src/components/MultiGestureCanvas/usePinchGesture.ts" "@typescript-eslint/no-deprecated/LegacyPinchGesture" 1 -"../../src/components/MultiGestureCanvas/usePinchGesture.ts" "react-hooks/set-state-in-effect" 1 -"../../src/components/MultiGestureCanvas/useTapGestures.ts" "@typescript-eslint/no-deprecated/Gesture.Tap" 4 -"../../src/components/MultiGestureCanvas/useTapGestures.ts" "@typescript-eslint/no-deprecated/LegacyTapGesture" 2 -"../../src/components/MultiGestureCanvas/useTapGestures.ts" "no-restricted-syntax" 1 +"../../src/components/MultiGestureCanvas/useCanvasPanGesture.ts" "no-restricted-syntax" 1 +"../../src/components/MultiGestureCanvas/useCanvasPinchGesture.ts" "react-hooks/set-state-in-effect" 1 +"../../src/components/MultiGestureCanvas/useCanvasTapGestures.ts" "no-restricted-syntax" 1 "../../src/components/Navigation/QuickCreationActionsBar/index.tsx" "no-restricted-imports" 1 "../../src/components/NumberWithSymbolForm.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/NumberWithSymbolForm.tsx" "react-hooks/set-state-in-effect" 2 @@ -2246,6 +2225,7 @@ "../../tests/ui/CategorySelectorTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 8 "../../tests/ui/ChangeReceiptBillingAccountPageTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../tests/ui/ChatActionableButtonsTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../tests/ui/ChatTransactionPreviewTest.tsx" "@typescript-eslint/no-deprecated/originalMessage" 1 "../../tests/ui/ChronosTimerHeaderButton.test.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/ui/ClearReportActionErrorsUITest.tsx" "@typescript-eslint/no-deprecated/originalMessage" 1 "../../tests/ui/components/CompactMenuContextTest.tsx" "@typescript-eslint/no-deprecated/MenuItem" 6 From 5316e0edc62bfbce5446b55f12672ad205b72884 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Mon, 26 Oct 2026 09:00:13 +0100 Subject: [PATCH 31/37] Re-baseline the oxlint seatbelt after merging main --- config/oxlint/oxlint.seatbelt.tsv | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/oxlint/oxlint.seatbelt.tsv b/config/oxlint/oxlint.seatbelt.tsv index f02ccd4c79af..546441937d55 100644 --- a/config/oxlint/oxlint.seatbelt.tsv +++ b/config/oxlint/oxlint.seatbelt.tsv @@ -475,7 +475,6 @@ "../../src/components/Table/TableBody.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../src/components/Table/TableContext.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 "../../src/components/Tables/WorkspaceCategoryRulesTable/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 -"../../src/components/Tables/WorkspaceCompanyCardsTable/WorkspaceCompanyCardsTableRow.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/Tables/WorkspaceListTable/TransferOwnershipFlow.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../src/components/TabSelector/getBackground.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/TagPicker/index.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 @@ -862,6 +861,7 @@ "../../src/libs/ExportOnyxState/masking.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../src/libs/ExportOnyxState/masking.ts" "@typescript-eslint/no-unsafe-type-assertion" 14 "../../src/libs/fileDownload/DownloadUtils.ts" "no-restricted-syntax" 1 +"../../src/libs/fileDownload/FileUtils.ts" "@typescript-eslint/no-misused-promises" 1 "../../src/libs/fileDownload/FileUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 4 "../../src/libs/fileDownload/getImageManipulator/index.native.ts" "@typescript-eslint/no-deprecated/manipulateAsync" 1 "../../src/libs/fileDownload/getImageManipulator/index.ts" "@typescript-eslint/no-deprecated/manipulateAsync" 1 @@ -1836,8 +1836,7 @@ "../../src/pages/workspace/companyCards/WorkspaceCompanyCardAddWorkEmailPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../src/pages/workspace/companyCards/WorkspaceCompanyCardAddWorkEmailPage.tsx" "no-restricted-imports" 1 "../../src/pages/workspace/companyCards/WorkspaceCompanyCardEditCardNamePage.tsx" "no-restricted-imports" 1 -"../../src/pages/workspace/companyCards/WorkspaceCompanyCardEditTransactionStartDatePage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 -"../../src/pages/workspace/companyCards/WorkspaceCompanyCardEditTransactionStartDatePage.tsx" "no-restricted-imports" 1 +"../../src/pages/workspace/companyCards/WorkspaceCompanyCardEditTransactionStartDatePage.tsx" "react-hooks/purity" 1 "../../src/pages/workspace/companyCards/WorkspaceCompanyCardFeedSelectorPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../src/pages/workspace/companyCards/WorkspaceCompanyCardFeedSelectorPage.tsx" "no-restricted-imports" 1 "../../src/pages/workspace/companyCards/WorkspaceCompanyCardPageEmptyState.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 @@ -2214,6 +2213,7 @@ "../../tests/perf-test/DynamicReportDetailsPage.perf-test.tsx" "@typescript-eslint/no-deprecated/originalMessage" 1 "../../tests/perf-test/DynamicReportDetailsPage.perf-test.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 "../../tests/perf-test/MoneyRequestReportActionsList.perf-test.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/perf-test/MoneyRequestReportTransactionRowWrites.perf-test.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/perf-test/ReportActionCompose.perf-test.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/ui/AddAgentRulePageTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../tests/ui/AgentRulesSectionTest.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 From 6eca4cc6ac18cb016af12092abf020181256ac4c Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Tue, 27 Oct 2026 08:37:55 +0100 Subject: [PATCH 32/37] Mirror the new no-unsafe-onyx-read, no-onyx-get-snapshot-key and no-manual-memoization rules in the oxlint config --- oxlint.config.mts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/oxlint.config.mts b/oxlint.config.mts index 8487a4d0c7a8..f37341886711 100644 --- a/oxlint.config.mts +++ b/oxlint.config.mts @@ -354,6 +354,8 @@ export default defineConfig({ 'rulesdir/require-a11y-disable-justification': 'error', 'rulesdir/require-live-region-for-status-updates': 'error', 'rulesdir/require-locale-for-localized-date-format': 'error', + 'rulesdir/no-unsafe-onyx-read': 'error', + 'rulesdir/no-onyx-get-snapshot-key': 'error', 'rulesdir/use-double-negation-instead-of-boolean': 'error', // Needs the type of the `&&` left operand, and jsPlugins get no type info. 'rulesdir/boolean-conditional-rendering': 'off', @@ -1065,7 +1067,15 @@ export default defineConfig({ files: ['server/victory-chart-renderer/**/*.{ts,tsx}'], rules: {'no-restricted-imports': ['error', {paths: victoryChartRendererRestrictedImportPaths, patterns: victoryChartRendererRestrictedImportPatterns}]}, }, - {files: ['src/**/*.{ts,tsx}'], rules: {'rulesdir/no-object-keys-includes': 'error', 'rulesdir/prefer-locale-compare-from-context': 'error'}}, + { + files: ['src/**/*.{ts,tsx}'], + rules: { + // The lint pipeline keeps this diagnostic only when both compilers memoize the file. + 'rulesdir/no-manual-memoization': 'error', + 'rulesdir/no-object-keys-includes': 'error', + 'rulesdir/prefer-locale-compare-from-context': 'error', + }, + }, { files: ['src/styles/typography.ts', 'src/styles/variables.ts'], rules: { From de96f4329c0c1cf565fba5f5a38b8d5dbdef4a75 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Tue, 27 Oct 2026 09:17:27 +0100 Subject: [PATCH 33/37] Re-baseline the oxlint seatbelt after merging main --- config/oxlint/oxlint.seatbelt.tsv | 52 +++++++++---------------------- 1 file changed, 14 insertions(+), 38 deletions(-) diff --git a/config/oxlint/oxlint.seatbelt.tsv b/config/oxlint/oxlint.seatbelt.tsv index 546441937d55..6985c8e3d634 100644 --- a/config/oxlint/oxlint.seatbelt.tsv +++ b/config/oxlint/oxlint.seatbelt.tsv @@ -106,15 +106,9 @@ "../../src/components/CategoryPicker/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/Charts/hooks/useChartLabelFormats.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/Charts/hooks/useChartLabelMeasurements.ts" "rulesdir/no-raw-typography" 1 -"../../src/components/Charts/utils/chartFontAssets.native.ts" "@typescript-eslint/no-unsafe-type-assertion" 13 -"../../src/components/Charts/utils/chartFontAssets.ts" "@typescript-eslint/no-unsafe-type-assertion" 13 -"../../src/components/Charts/utils/chartFontsCache.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 -"../../src/components/Charts/utils/chartWebFont.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 -"../../src/components/Charts/utils/getChartSkiaTypeface.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/CheckboxWithLabel.tsx" "no-restricted-syntax" 1 "../../src/components/ColumnsSettingsList.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/ColumnsSettingsList.tsx" "no-restricted-imports" 1 -"../../src/components/Composer/implementation/index.native.tsx" "react-hooks/preserve-manual-memoization" 1 "../../src/components/Composer/implementation/index.tsx" "react-hooks/set-state-in-effect" 2 "../../src/components/ConfirmModal.tsx" "@typescript-eslint/no-deprecated/ConfirmModal" 1 "../../src/components/ConnectionLayout.tsx" "no-restricted-imports" 1 @@ -344,7 +338,6 @@ "../../src/components/RenderHTML.tsx" "import/no-cycle" 1 "../../src/components/ReportActionAvatars/index.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../src/components/ReportActionAvatars/useReportPreviewSenderID.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 -"../../src/components/ReportActionItem/ExportWithDropdownMenu.tsx" "react-hooks/preserve-manual-memoization" 3 "../../src/components/ReportActionItem/MoneyRequestReceiptView.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../src/components/ReportActionItem/MoneyRequestReceiptView.tsx" "react-hooks/set-state-in-effect" 1 "../../src/components/ReportActionItem/MoneyRequestView.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 5 @@ -398,14 +391,10 @@ "../../src/components/Search/SearchContextDefinitions.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/Search/SearchList/ListItem/BaseListItemHeader.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../src/components/Search/SearchList/ListItem/BaseListItemHeader.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 -"../../src/components/Search/SearchList/ListItem/CardListItemHeader.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 -"../../src/components/Search/SearchList/ListItem/CardListItemHeader.tsx" "@typescript-eslint/no-unsafe-type-assertion" 4 "../../src/components/Search/SearchList/ListItem/ChatListItem.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/Search/SearchList/ListItem/ExpenseReportListItem.tsx" "@typescript-eslint/no-unsafe-type-assertion" 4 "../../src/components/Search/SearchList/ListItem/ExpenseReportListItemRow/ExpenseReportListItemRowWide.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/Search/SearchList/ListItem/GroupChildrenContent.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 -"../../src/components/Search/SearchList/ListItem/MemberListItemHeader.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 -"../../src/components/Search/SearchList/ListItem/MemberListItemHeader.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../src/components/Search/SearchList/ListItem/ReportListItemHeader.tsx" "@typescript-eslint/no-unsafe-type-assertion" 4 "../../src/components/Search/SearchList/ListItem/SearchMergeReportsListItem.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/Search/SearchList/ListItem/TaskListItem.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 @@ -417,8 +406,6 @@ "../../src/components/Search/SearchList/ListItem/TransactionListItem/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 4 "../../src/components/Search/SearchList/ListItem/TransactionListItem/TransactionListItemNarrow.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/Search/SearchList/ListItem/TransactionListItem/TransactionListItemWide.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 -"../../src/components/Search/SearchList/ListItem/WithdrawalIDListItemHeader.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 -"../../src/components/Search/SearchList/ListItem/WithdrawalIDListItemHeader.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../src/components/Search/SearchPageHeader/DatePickerFilterPopup.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../src/components/Search/SearchPageHeader/SearchFilterBar.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 "../../src/components/Search/SearchRouter/buildSubstitutionsMap.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 @@ -431,7 +418,6 @@ "../../src/components/Search/SearchTableHeader.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 "../../src/components/SelectionButton.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/SelectionList/BaseSelectionList.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 -"../../src/components/SelectionList/components/Footer.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/SelectionList/ListItem/SplitListItem.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/SelectionList/SelectionListWithSections/BaseSelectionListWithSections.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../src/components/SelectionList/SelectionListWithSections/BaseSelectionListWithSections.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 @@ -549,7 +535,6 @@ "../../src/CONST/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/CONST/runtimeConfigured.ts" "import/no-named-as-default" 1 "../../src/CONST/runtimeConfigured.ts" "rulesdir/no-direct-personal-details-list" 1 -"../../src/DeepLinkHandler.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../src/DeepLinkHandler.tsx" "no-restricted-syntax" 1 "../../src/Expensify.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../src/Expensify.tsx" "no-restricted-syntax" 1 @@ -574,7 +559,6 @@ "../../src/hooks/useCardFeedsForDisplay.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/hooks/useCardFeedsForDisplay.ts" "no-restricted-imports" 1 "../../src/hooks/useChangeTransactionsReportReports.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 -"../../src/hooks/useCurrentReportID.tsx" "react-hooks/preserve-manual-memoization" 1 "../../src/hooks/useDebounce.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/hooks/useDebouncedValue.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/hooks/useDebounceNonReactive.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 @@ -613,7 +597,9 @@ "../../src/hooks/usePrevious.ts" "react-hooks/refs" 1 "../../src/hooks/useReportScrollManager/index.native.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/hooks/useResetBankAccountModal.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/hooks/useResponsiveLayout/index.native.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../src/hooks/useResponsiveLayout/index.native.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/hooks/useResponsiveLayout/index.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../src/hooks/useResponsiveLayout/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/hooks/useRestoreWorkspacesTabOnNavigate.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/hooks/useReviewDuplicatesNavigation.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 @@ -830,7 +816,7 @@ "../../src/libs/CardFeedUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 7 "../../src/libs/CardFeedUtils.ts" "import/no-cycle" 2 "../../src/libs/CardUtils.ts" "@typescript-eslint/no-unnecessary-type-assertion" 3 -"../../src/libs/CardUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 12 +"../../src/libs/CardUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 10 "../../src/libs/CategoryOptionListUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/libs/ChronosUtils.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../src/libs/clearSelectedTextIfComposerBlurred/clearSelectedTextIfComposerBlurred.web.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 @@ -951,14 +937,11 @@ "../../src/libs/Navigation/helpers/dynamicRoutesUtils/getDynamicRouteQueryParams.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/libs/Navigation/helpers/dynamicRoutesUtils/getPathWithoutDynamicSuffix.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/libs/Navigation/helpers/dynamicRoutesUtils/stripDynamicRouteSuffixFromPath.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/libs/Navigation/helpers/enableGlobalReimbursementsNavigationUtils.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../src/libs/Navigation/helpers/findFocusedRouteWithOnyxTabGuard.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 -"../../src/libs/Navigation/helpers/getAdaptedStateFromPath.ts" "@typescript-eslint/no-unnecessary-type-assertion" 3 -"../../src/libs/Navigation/helpers/getAdaptedStateFromPath.ts" "@typescript-eslint/no-unsafe-type-assertion" 4 "../../src/libs/Navigation/helpers/getAdaptedStateFromPath.ts" "import/no-cycle" 1 "../../src/libs/Navigation/helpers/getOnboardingRouteFromScreen.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../src/libs/Navigation/helpers/getParamsFromRoute.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 -"../../src/libs/Navigation/helpers/getPathFromState.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 -"../../src/libs/Navigation/helpers/getPathFromState.ts" "@typescript-eslint/no-unsafe-type-assertion" 6 "../../src/libs/Navigation/helpers/getStateFromPath.ts" "@typescript-eslint/no-unsafe-type-assertion" 4 "../../src/libs/Navigation/helpers/getTopmostFullScreenRoute.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/libs/Navigation/helpers/getTopmostReportParams.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 @@ -967,8 +950,6 @@ "../../src/libs/Navigation/helpers/isRHPOnSearchMoneyRequestReportPage.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/libs/Navigation/helpers/isTabRouteAtRoot.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/libs/Navigation/helpers/lastVisitedTabPathUtils/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 -"../../src/libs/Navigation/helpers/linkTo/index.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 -"../../src/libs/Navigation/helpers/linkTo/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 15 "../../src/libs/Navigation/helpers/navigateToWorkspacesPage.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../src/libs/Navigation/helpers/navigateToWorkspacesPage.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/libs/Navigation/helpers/popReportsSplitNavigatorToReport.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 @@ -1045,7 +1026,7 @@ "../../src/libs/ReportUtils.ts" "@typescript-eslint/no-deprecated/getReportTransactions" 16 "../../src/libs/ReportUtils.ts" "@typescript-eslint/no-deprecated/originalMessage" 23 "../../src/libs/ReportUtils.ts" "@typescript-eslint/no-deprecated/translateLocal" 7 -"../../src/libs/ReportUtils.ts" "@typescript-eslint/no-unnecessary-type-assertion" 15 +"../../src/libs/ReportUtils.ts" "@typescript-eslint/no-unnecessary-type-assertion" 16 "../../src/libs/ReportUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 31 "../../src/libs/ReportUtils.ts" "import/no-cycle" 5 "../../src/libs/ReportUtils.ts" "rulesdir/no-onyx-connect" 13 @@ -1182,8 +1163,6 @@ "../../src/pages/EnablePayments/shared/IdologyQuestions.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/pages/EnablePayments/Wallet/AddBankAccount/AddBankAccount.tsx" "no-restricted-imports" 1 "../../src/pages/EnablePayments/Wallet/EnablePayments.tsx" "no-restricted-imports" 1 -"../../src/pages/EnablePayments/Wallet/PersonalInfo/substeps/DateOfBirthStep.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 -"../../src/pages/EnablePayments/Wallet/PersonalInfo/substeps/DateOfBirthStep.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 "../../src/pages/EnablePayments/Wallet/utils/getSubstepValues.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../src/pages/EnablePayments/Wallet/VerifyIdentity/VerifyIdentity.tsx" "no-restricted-imports" 1 "../../src/pages/GroupChatNameEditPage.tsx" "no-restricted-imports" 1 @@ -1237,7 +1216,6 @@ "../../src/pages/inbox/report/shouldUseEmojiPickerSelection/index.web.ts" "no-restricted-syntax" 1 "../../src/pages/inbox/report/TripSummary.tsx" "rulesdir/no-default-id-values" 1 "../../src/pages/inbox/report/useDebouncedSaveDraft.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 -"../../src/pages/inbox/report/useReportUnreadMessageScrollTracking.ts" "react-hooks/preserve-manual-memoization" 1 "../../src/pages/inbox/report/WhisperBanner.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/pages/inbox/report/withReportAndPrivateNotesOrNotFound.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/pages/inbox/report/withReportAndPrivateNotesOrNotFound.tsx" "no-restricted-imports" 1 @@ -1267,7 +1245,6 @@ "../../src/pages/iou/request/ParticipantSearchResults.tsx" "@typescript-eslint/no-unsafe-type-assertion" 4 "../../src/pages/iou/request/step/DistanceManualTabContent.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/pages/iou/request/step/DynamicIOURequestEditReport.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 -"../../src/pages/iou/request/step/DynamicIOURequestStepDistance.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 3 "../../src/pages/iou/request/step/DynamicIOURequestStepDistance.tsx" "react-hooks/set-state-in-effect" 1 "../../src/pages/iou/request/step/DynamicIOURequestStepReport.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../src/pages/iou/request/step/DynamicIOURequestStepSubrate.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 @@ -1281,12 +1258,10 @@ "../../src/pages/iou/request/step/IOURequestEditReportCommon.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/pages/iou/request/step/IOURequestStepAmount.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../src/pages/iou/request/step/IOURequestStepAmount.tsx" "react-hooks/set-state-in-effect" 1 -"../../src/pages/iou/request/step/IOURequestStepConfirmation/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 +"../../src/pages/iou/request/step/IOURequestStepConfirmation/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../src/pages/iou/request/step/IOURequestStepConfirmation/index.tsx" "no-restricted-imports" 1 -"../../src/pages/iou/request/step/IOURequestStepConfirmation/index.tsx" "react-hooks/preserve-manual-memoization" 2 "../../src/pages/iou/request/step/IOURequestStepCurrencyModal.tsx" "no-restricted-imports" 1 "../../src/pages/iou/request/step/IOURequestStepDistanceGPS/Waypoints/index.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 2 -"../../src/pages/iou/request/step/IOURequestStepDistanceMap.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 3 "../../src/pages/iou/request/step/IOURequestStepDistanceMap.tsx" "react-hooks/set-state-in-effect" 1 "../../src/pages/iou/request/step/IOURequestStepOdometerImage/index.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 "../../src/pages/iou/request/step/IOURequestStepOdometerImage/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 @@ -1426,7 +1401,6 @@ "../../src/pages/settings/Agents/Fields/DynamicEditNamePage.tsx" "no-restricted-imports" 1 "../../src/pages/settings/Agents/Fields/DynamicEditPromptPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/pages/settings/Agents/Fields/DynamicEditPromptPage.tsx" "no-restricted-imports" 1 -"../../src/pages/settings/AppDownloadLinks.tsx" "no-restricted-imports" 1 "../../src/pages/settings/Copilot/CopilotPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../src/pages/settings/Copilot/CopilotPage.tsx" "no-restricted-imports" 1 "../../src/pages/settings/DynamicKeyboardShortcutsPage.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 @@ -1448,7 +1422,6 @@ "../../src/pages/settings/Profile/Contacts/DynamicContactMethodsPage.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 "../../src/pages/settings/Profile/Contacts/DynamicContactMethodsPage.tsx" "no-restricted-imports" 1 "../../src/pages/settings/Profile/Contacts/DynamicNewContactMethodPage.tsx" "no-restricted-imports" 1 -"../../src/pages/settings/Profile/Contacts/DynamicNewContactMethodPage.tsx" "react-hooks/preserve-manual-memoization" 1 "../../src/pages/settings/Profile/CustomStatus/SetDatePage.tsx" "no-restricted-imports" 1 "../../src/pages/settings/Profile/CustomStatus/SetDatePage.tsx" "react-hooks/purity" 1 "../../src/pages/settings/Profile/CustomStatus/SetTimePage.tsx" "no-restricted-imports" 1 @@ -1557,6 +1530,7 @@ "../../src/pages/settings/Wallet/PaymentMethodList.tsx" "@typescript-eslint/no-unsafe-type-assertion" 7 "../../src/pages/settings/Wallet/PaymentMethodListItem.tsx" "@typescript-eslint/no-deprecated/MenuItem" 1 "../../src/pages/settings/Wallet/PersonalCardDetailsHeaderMenu.tsx" "@typescript-eslint/no-deprecated/MenuItem" 2 +"../../src/pages/settings/Wallet/PersonalCardDetailsPage.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../src/pages/settings/Wallet/PersonalCardDetailsPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../src/pages/settings/Wallet/PersonalCardDetailsPage.tsx" "no-restricted-imports" 1 "../../src/pages/settings/Wallet/PersonalCardEditNamePage.tsx" "no-restricted-imports" 1 @@ -2062,14 +2036,12 @@ "../../src/pages/workspace/WorkspaceOverviewSharePage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/pages/workspace/WorkspaceOverviewSharePage.tsx" "no-restricted-imports" 1 "../../src/pages/workspace/WorkspacePageWithSections.tsx" "no-restricted-imports" 2 -"../../src/pages/workspace/WorkspacePageWithSections.tsx" "react-hooks/preserve-manual-memoization" 2 "../../src/polyfills/requestIdleCallback.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../src/ROUTES.ts" "@typescript-eslint/no-deprecated/getUrlWithBackToParam" 53 "../../src/ROUTES.ts" "@typescript-eslint/no-unsafe-type-assertion" 3 "../../src/selectors/AdvancedSearchFiltersForm.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/selectors/Domain.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/selectors/Policy.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 -"../../src/setup/addUtilsToWindow.ts" "@typescript-eslint/no-unsafe-type-assertion" 20 "../../src/setup/index.ts" "import/no-named-as-default" 1 "../../src/setup/moduleInitPolyfill.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/setup/platformSetup/index.native.ts" "no-empty-function" 1 @@ -2204,10 +2176,11 @@ "../../tests/navigation/cleanStaleReportActionBackToParamTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/navigation/ensureTabNavigatorRoutesTests.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/navigation/findFocusedRouteWithOnyxTabGuardTests.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 -"../../tests/navigation/getPathFromStateTests.ts" "@typescript-eslint/no-unnecessary-type-assertion" 3 +"../../tests/navigation/getPathFromStateTests.ts" "@typescript-eslint/no-unnecessary-type-assertion" 6 +"../../tests/navigation/getStateFromPathTests.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/navigation/LinkedActionNotFoundGuardTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/navigation/OneTransactionThreadRedirectHandlerOnyxTest.tsx" "@typescript-eslint/no-deprecated/originalMessage" 1 -"../../tests/navigation/splitExpenseDynamicRouteTests.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/navigation/splitExpenseDynamicRouteTests.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 "../../tests/perf-local/OptionsListChatPreview.perf-local.ts" "@typescript-eslint/no-unnecessary-type-assertion" 5 "../../tests/perf-local/OptionsListChatPreview.perf-local.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../tests/perf-test/DynamicReportDetailsPage.perf-test.tsx" "@typescript-eslint/no-deprecated/originalMessage" 1 @@ -2221,6 +2194,7 @@ "../../tests/ui/AgentsPromoBannersTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/ui/AncestorReportActionItemTest.tsx" "@typescript-eslint/no-deprecated/originalMessage" 1 "../../tests/ui/ApproverSelectionListTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 3 +"../../tests/ui/AssignCardAssigneeStepTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/ui/BaseModalTest.tsx" "react-hooks/globals" 1 "../../tests/ui/CategorySelectorTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 8 "../../tests/ui/ChangeReceiptBillingAccountPageTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 @@ -2254,6 +2228,7 @@ "../../tests/ui/IOURequestStepDistanceOdometerSflResumeTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../tests/ui/IOURequestStepDistanceRateTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 4 "../../tests/ui/IOURequestStepDistanceTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/ui/IssueNewCardAssigneeStepTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/ui/IssueNewCardAssigneeStepTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../tests/ui/IssueNewCardConfirmValidateCodePageTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../tests/ui/IssueNewCardConfirmValidateCodePageTest.tsx" "no-unsafe-optional-chaining" 1 @@ -2307,6 +2282,7 @@ "../../tests/unit/AccountSwitcherSkeletonViewTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/unit/activeSpansSweepOrderTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/unit/AddExistingExpenseTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/unit/addUtilsToWindowTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 6 "../../tests/unit/ApiUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 4 "../../tests/unit/APIWriteWhenReadyTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 3 "../../tests/unit/backgroundLocationTrackingTaskTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 @@ -2457,7 +2433,7 @@ "../../tests/unit/ReportActionsUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 38 "../../tests/unit/ReportAlternateTextParityTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 15 "../../tests/unit/ReportAlternateTextParityTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 -"../../tests/unit/ReportAlternateTextUtilsTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 68 +"../../tests/unit/ReportAlternateTextUtilsTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 69 "../../tests/unit/ReportAlternateTextUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 "../../tests/unit/reportAttributesTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 2 "../../tests/unit/ReportNameUtilsTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 34 From 6c0c0de9644f7d50ed8125b8ea76684d2d49e523 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Tue, 27 Oct 2026 09:32:19 +0100 Subject: [PATCH 34/37] Drop the temporary oxlint release-age exclusions and the unused vite-plus override --- .npmrc | 5 ----- package.json | 1 - 2 files changed, 6 deletions(-) diff --git a/.npmrc b/.npmrc index 8f9e4e59c1bd..2a14ea859d36 100644 --- a/.npmrc +++ b/.npmrc @@ -10,8 +10,3 @@ min-release-age-exclude[]=@expensify/react-native-wallet min-release-age-exclude[]=react-fast-pdf min-release-age-exclude[]=react-native-key-command min-release-age-exclude[]=react-native-qrcode-svg -# List each native binding scope too, or npm silently skips the optional dependency. -min-release-age-exclude[]=oxlint -min-release-age-exclude[]=@oxlint/* -min-release-age-exclude[]=oxc-transform-react -min-release-age-exclude[]=@oxc-transform-react/* diff --git a/package.json b/package.json index 4750ff2e87aa..139c3e6289c6 100644 --- a/package.json +++ b/package.json @@ -376,7 +376,6 @@ }, "overrides": { "typescript": "$typescript", - "vite-plus": "^0.1.15", "awesome-phonenumber": "$awesome-phonenumber", "@typescript-eslint/eslint-plugin": "8.58.1", "@typescript-eslint/parser": "8.58.1", From eff851a08a48d5144d3a10acae5e25b4e6cd1367 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Tue, 27 Oct 2026 10:45:47 +0100 Subject: [PATCH 35/37] Share one React Compiler verdict across lint and trim the oxlint bridge to the validations oxc lacks --- config/oxlint/reactCompilerGate.mjs | 15 ++++-------- config/oxlint/reactCompilerRust.mjs | 24 ++++++------------- config/reactCompiler/checkWithOxc.d.mts | 3 +++ config/reactCompiler/checkWithOxc.mjs | 23 ++++++++++++++++++ .../lint/processors/ReactCompilerFilter.ts | 8 +++---- .../lint/processors/ReactCompilerWorker.ts | 4 ++-- 6 files changed, 43 insertions(+), 34 deletions(-) diff --git a/config/oxlint/reactCompilerGate.mjs b/config/oxlint/reactCompilerGate.mjs index 280579c9f6b2..b648000070d8 100644 --- a/config/oxlint/reactCompilerGate.mjs +++ b/config/oxlint/reactCompilerGate.mjs @@ -3,24 +3,19 @@ * For ESLint, scripts/lint's ReactCompilerFilter drops those reports. oxlint has no processors and * can't filter its native rules, so here the JS plugin rule itself is wrapped and drops them. */ -import path from 'node:path'; - -import checkReactCompilerWithOxc from '../reactCompiler/checkWithOxc.mjs'; +import {isFileMemoized} from '../reactCompiler/checkWithOxc.mjs'; const memoizationCache = new Map(); /** - * Tells whether React Compiler memoizes the whole file. Compiling is slow, so - * each file is checked once. Tests and node_modules count as not memoized, as in ReactCompilerFilter. + * The lint pipeline's memoization verdict, remembered per file because compiling is slow. * @param {string} filename * @param {string} sourceText * @returns {boolean} */ -function isFileMemoized(filename, sourceText) { +function isFileMemoizedOnce(filename, sourceText) { if (!memoizationCache.has(filename)) { - const normalized = filename.split(path.sep).join('/'); - const skipped = normalized.includes('/tests/') || normalized.startsWith('tests/') || normalized.includes('node_modules/'); - memoizationCache.set(filename, skipped ? false : checkReactCompilerWithOxc(sourceText, filename).memoized); + memoizationCache.set(filename, isFileMemoized(sourceText, filename)); } return memoizationCache.get(filename); } @@ -62,7 +57,7 @@ function withGating(rule, shouldGate) { if (shouldGate(rule, args[0])) { const filename = context.filename ?? context.getFilename(); const sourceText = (context.sourceCode ?? context.getSourceCode()).text; - if (isFileMemoized(filename, sourceText)) { + if (isFileMemoizedOnce(filename, sourceText)) { return; } } diff --git a/config/oxlint/reactCompilerRust.mjs b/config/oxlint/reactCompilerRust.mjs index 267d9004f893..bf0cab081773 100644 --- a/config/oxlint/reactCompilerRust.mjs +++ b/config/oxlint/reactCompilerRust.mjs @@ -6,6 +6,8 @@ import path from 'node:path'; import {transformSync} from 'oxc-transform-react'; +import oxcReactCompilerConfig from '../babel/oxcReactCompilerConfig.js'; + const RULE_BY_CATEGORY = { Refs: 'refs', EffectSetState: 'set-state-in-effect', @@ -43,23 +45,14 @@ const IGNORED_CATEGORIES = new Set([ const COMPILATION_SKIPPED_CATEGORIES = new Set(['EffectDependencies', 'IncompatibleLibrary', 'PreserveManualMemo', 'UnsupportedSyntax']); +// The validations eslint-plugin-react-hooks turns on that oxc does not. The rest are already oxc +// defaults or land in IGNORED_CATEGORIES. const ENVIRONMENT = { - validateRefAccessDuringRender: true, - validateNoSetStateInRender: true, validateNoSetStateInEffects: true, // The plugin spells this `validateNoJSXInTryStatements`; oxc spells it with a lowercase `sx`. validateNoJsxInTryStatements: true, validateNoImpureFunctionsInRender: true, validateStaticComponents: true, - validateNoFreezingKnownMutableFunctions: true, - validateNoVoidUseMemo: true, - validateNoCapitalizedCalls: [], - validateHooksUsage: true, - validateNoDerivedComputationsInEffects: true, - enableUseKeyedState: false, - enableVerboseNoSetStateInEffect: false, - validateExhaustiveEffectDependencies: 'off', - enableTreatRefLikeIdentifiersAsRefs: true, }; // The category is only in the formatted `codeframe` string, which is not a public API of @@ -174,19 +167,16 @@ function buildMessage(category, error) { function analyze(filename, sourceText) { let result; try { + const buildOptions = oxcReactCompilerConfig(); result = transformSync(filename, sourceText, { lang: getLang(path.extname(filename).slice(1)), reactCompiler: { - target: '19', + ...buildOptions, outputMode: 'lint', // Without it the binding returns only fatal diagnostics. reportDiagnostics: true, flowSuppressions: false, - // Without it the compiler skips every function under an `eslint-disable-next-line - // react-hooks/exhaustive-deps` comment. Lint-only: in the build configs it would change - // what ships memoized. - eslintSuppressionRules: [], - environment: ENVIRONMENT, + environment: {...buildOptions.environment, ...ENVIRONMENT}, }, }); } catch (error) { diff --git a/config/reactCompiler/checkWithOxc.d.mts b/config/reactCompiler/checkWithOxc.d.mts index 48c2f6b3155e..507fb4104ff9 100644 --- a/config/reactCompiler/checkWithOxc.d.mts +++ b/config/reactCompiler/checkWithOxc.d.mts @@ -16,6 +16,9 @@ type CompilationResult = { }; declare function checkReactCompilerWithOxc(source: string, filename: string): CompilationResult; +declare function shouldSkipReactCompiler(filename: string): boolean; +declare function isFileMemoized(source: string, filename: string): boolean; export type {CompilationResult, CompilerError}; +export {isFileMemoized, shouldSkipReactCompiler}; export default checkReactCompilerWithOxc; diff --git a/config/reactCompiler/checkWithOxc.mjs b/config/reactCompiler/checkWithOxc.mjs index 6056c39878aa..5c196ac8cdf3 100644 --- a/config/reactCompiler/checkWithOxc.mjs +++ b/config/reactCompiler/checkWithOxc.mjs @@ -137,4 +137,27 @@ function checkReactCompilerWithOxc(source, filename) { } } +/** + * Paths lint never compiles: tests and node_modules. Shared by the lint pipeline, its worker and the + * oxlint gate so every caller skips the same files. + * @param {string} filename + * @returns {boolean} + */ +function shouldSkipReactCompiler(filename) { + const normalized = filename.split(path.sep).join('/'); + return normalized.includes('/tests/') || normalized.startsWith('tests/') || normalized.includes('node_modules/'); +} + +/** + * The one memoization verdict lint uses: false for skipped paths, otherwise whether the compiler + * emitted a memo cache for the whole file. + * @param {string} source + * @param {string} filename + * @returns {boolean} + */ +function isFileMemoized(source, filename) { + return !shouldSkipReactCompiler(filename) && checkReactCompilerWithOxc(source, filename).memoized; +} + +export {isFileMemoized, shouldSkipReactCompiler}; export default checkReactCompilerWithOxc; diff --git a/scripts/lint/processors/ReactCompilerFilter.ts b/scripts/lint/processors/ReactCompilerFilter.ts index 3b60a7b6b80e..5464c3fbd96c 100644 --- a/scripts/lint/processors/ReactCompilerFilter.ts +++ b/scripts/lint/processors/ReactCompilerFilter.ts @@ -3,6 +3,8 @@ import path from 'node:path'; import type {LintMessage, ProcessorContext} from '../types'; +// eslint-disable-next-line import/extensions +import {shouldSkipReactCompiler} from '../../../config/reactCompiler/checkWithOxc.mjs'; // eslint-disable-next-line import/extensions import {EXHAUSTIVE_DEPS_USECALLBACK_USEMEMO_PATTERN, RULES_SUPPRESSED_BY_REACT_COMPILER} from '../../../config/reactCompiler/suppressedRules.mjs'; import WorkerPool from '../../utils/WorkerPool'; @@ -29,10 +31,6 @@ function isSuppressibleMessage(message: LintMessage): boolean { return message.ruleID === 'react-hooks/exhaustive-deps' && EXHAUSTIVE_DEPS_USECALLBACK_USEMEMO_PATTERN.test(message.message); } -function shouldSkipCompiler(filename: string): boolean { - return filename.includes('/tests/') || filename.includes('node_modules/'); -} - function cachePath(projectRoot: string, hash: string): string { return `${projectRoot}/${CACHE_DIR}/${hash}`; } @@ -160,7 +158,7 @@ async function filterReactCompilerMessages( const candidateNames: string[] = []; for (const [filename, fileMessages] of byFile) { - if (shouldSkipCompiler(filename)) { + if (shouldSkipReactCompiler(filename)) { continue; } if (fileMessages.some((message) => isSuppressibleMessage(message) || message.ruleID === MANUAL_MEMOIZATION_RULE)) { diff --git a/scripts/lint/processors/ReactCompilerWorker.ts b/scripts/lint/processors/ReactCompilerWorker.ts index 0eacb2d3d474..8f62116332d7 100644 --- a/scripts/lint/processors/ReactCompilerWorker.ts +++ b/scripts/lint/processors/ReactCompilerWorker.ts @@ -1,6 +1,6 @@ // The compiler helper is ESM (.mjs); Bun resolves it without the extension. // eslint-disable-next-line import/extensions -import checkReactCompilerWithOxc from '../../../config/reactCompiler/checkWithOxc.mjs'; +import {isFileMemoized} from '../../../config/reactCompiler/checkWithOxc.mjs'; type WorkerRequest = { filename: string; @@ -18,7 +18,7 @@ declare const self: Worker; self.onmessage = (event: MessageEvent) => { const {filename, source} = event.data; try { - const response: WorkerResponse = {filename, memoized: checkReactCompilerWithOxc(source, filename).memoized, cacheable: true}; + const response: WorkerResponse = {filename, memoized: isFileMemoized(source, filename), cacheable: true}; postMessage(response); } catch { // Conservative: treat a compiler crash as "not memoized" so this file From de1452ad3185dae77390b0818648d6ffd7777d66 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Tue, 27 Oct 2026 12:00:03 +0100 Subject: [PATCH 36/37] Re-baseline the oxlint seatbelt after merging main --- config/oxlint/oxlint.seatbelt.tsv | 1 - 1 file changed, 1 deletion(-) diff --git a/config/oxlint/oxlint.seatbelt.tsv b/config/oxlint/oxlint.seatbelt.tsv index 6985c8e3d634..29aed95ca87a 100644 --- a/config/oxlint/oxlint.seatbelt.tsv +++ b/config/oxlint/oxlint.seatbelt.tsv @@ -41,7 +41,6 @@ "../../scripts/compareKnipReports.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../scripts/createOrUpdateDeployChecklist.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../scripts/generateTranslations.ts" "@typescript-eslint/no-unsafe-type-assertion" 8 -"../../scripts/react-compiler-compliance-check.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../scripts/react-compiler-compliance-check.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../scripts/sentry/release-adoption-older-than.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../scripts/utils/Git.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 From 6d0fcfe385b687afd090e0645405808b4f1d04a3 Mon Sep 17 00:00:00 2001 From: Lukasz Modzelewski Date: Tue, 27 Oct 2026 09:50:55 +0100 Subject: [PATCH 37/37] Bump oxlint, oxlint-tsgolint and oxc-transform-react and re-baseline the oxlint seatbelt --- .npmrc | 7 + config/oxlint/oxlint.seatbelt.tsv | 61 +---- config/oxlint/plugins/core-rules.mjs | 2 +- package-lock.json | 382 +++++++++++++-------------- package.json | 6 +- tests/tooling/lintPipeline.test.ts | 2 +- 6 files changed, 206 insertions(+), 254 deletions(-) diff --git a/.npmrc b/.npmrc index 2a14ea859d36..487c5e8c89be 100644 --- a/.npmrc +++ b/.npmrc @@ -10,3 +10,10 @@ min-release-age-exclude[]=@expensify/react-native-wallet min-release-age-exclude[]=react-fast-pdf min-release-age-exclude[]=react-native-key-command min-release-age-exclude[]=react-native-qrcode-svg +# List each native binding scope too, or npm silently skips the optional dependency. +min-release-age-exclude[]=oxlint +min-release-age-exclude[]=@oxlint/* +min-release-age-exclude[]=oxc-transform-react +min-release-age-exclude[]=@oxc-transform-react/* +min-release-age-exclude[]=oxlint-tsgolint +min-release-age-exclude[]=@oxlint-tsgolint/* diff --git a/config/oxlint/oxlint.seatbelt.tsv b/config/oxlint/oxlint.seatbelt.tsv index 29aed95ca87a..f747cb0fee82 100644 --- a/config/oxlint/oxlint.seatbelt.tsv +++ b/config/oxlint/oxlint.seatbelt.tsv @@ -149,9 +149,7 @@ "../../src/components/EditableCell/usePopoverEditState.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/EmbeddedDemo/index.native.tsx" "import/no-named-as-default" 1 "../../src/components/EmojiPicker/EmojiPicker.tsx" "react-hooks/refs" 3 -"../../src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx" "react-hooks/preserve-manual-memoization" 1 "../../src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx" "react-hooks/refs" 2 -"../../src/components/EmojiPicker/EmojiPickerMenu/index.tsx" "react-hooks/preserve-manual-memoization" 1 "../../src/components/EmojiPicker/EmojiPickerMenu/index.tsx" "react-hooks/refs" 2 "../../src/components/EmojiPicker/EmojiPickerMenu/useEmojiPickerMenu.ts" "react-hooks/set-state-in-effect" 2 "../../src/components/EmojiPicker/EmojiSkinToneList.tsx" "react-hooks/set-state-in-effect" 2 @@ -207,7 +205,6 @@ "../../src/components/Icon/IllustrationLoader.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/Image/BaseImage.native.tsx" "@typescript-eslint/no-unsafe-type-assertion" 4 "../../src/components/Image/BaseImage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 4 -"../../src/components/Image/index.tsx" "react-hooks/preserve-manual-memoization" 2 "../../src/components/Image/index.tsx" "react-hooks/purity" 1 "../../src/components/Image/index.tsx" "react-hooks/refs" 1 "../../src/components/ImageSVG/index.android.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 @@ -220,7 +217,6 @@ "../../src/components/InitialURLContextProvider.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/InteractiveStepWrapper.tsx" "no-restricted-imports" 1 "../../src/components/KYCWall/BaseKYCWall.tsx" "@typescript-eslint/no-unsafe-type-assertion" 3 -"../../src/components/KYCWall/BaseKYCWall.tsx" "react-hooks/preserve-manual-memoization" 1 "../../src/components/KYCWall/BaseKYCWall.tsx" "react-hooks/refs" 2 "../../src/components/LHNOptionsList/LHNEmptyState.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/LHNOptionsList/OptionRowLHN/OptionRow/Pressable.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 @@ -381,7 +377,7 @@ "../../src/components/Search/hooks/useSearchSnapshot.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../src/components/Search/hooks/useStableOptimisticSortedData.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/Search/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 4 -"../../src/components/Search/index.tsx" "react-hooks/preserve-manual-memoization" 5 +"../../src/components/Search/index.tsx" "react-hooks/preserve-manual-memoization" 2 "../../src/components/Search/index.tsx" "react-hooks/refs" 5 "../../src/components/Search/index.tsx" "react-hooks/set-state-in-effect" 1 "../../src/components/Search/SearchAutocompleteList.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 @@ -490,7 +486,6 @@ "../../src/components/TimeModalPicker.tsx" "no-restricted-imports" 1 "../../src/components/TimePicker/TimePicker.tsx" "@typescript-eslint/no-unsafe-type-assertion" 6 "../../src/components/TimePicker/TimePicker.tsx" "react-hooks/set-state-in-effect" 1 -"../../src/components/Tooltip/BaseGenericTooltip/index.native.tsx" "react-hooks/preserve-manual-memoization" 1 "../../src/components/Tooltip/BaseGenericTooltip/index.native.tsx" "react-hooks/refs" 2 "../../src/components/Tooltip/BaseGenericTooltip/index.tsx" "react-hooks/immutability" 1 "../../src/components/Tooltip/BaseGenericTooltip/index.tsx" "react-hooks/refs" 2 @@ -566,7 +561,6 @@ "../../src/hooks/useDynamicForwardPath.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../src/hooks/useExportedToFilterOptions.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/hooks/useFilterFormValues.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 -"../../src/hooks/useHtmlPaste/index.ts" "react-hooks/preserve-manual-memoization" 1 "../../src/hooks/useHydrateReportsFromSnapshot.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/hooks/useImportSpreadsheetConfirmModal.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/hooks/useInitial.ts" "react-hooks/refs" 2 @@ -578,7 +572,6 @@ "../../src/hooks/useLazyAsset.ts" "@typescript-eslint/no-unsafe-type-assertion" 3 "../../src/hooks/useLazyAsset.ts" "react-hooks/set-state-in-effect" 1 "../../src/hooks/useMarkdownStyle.ts" "rulesdir/no-raw-typography" 3 -"../../src/hooks/useNewTransactions.ts" "react-hooks/preserve-manual-memoization" 3 "../../src/hooks/useNewTransactions.ts" "react-hooks/refs" 2 "../../src/hooks/useOnyx.ts" "@typescript-eslint/no-unsafe-type-assertion" 10 "../../src/hooks/useOptimisticDraftTransactions.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 @@ -587,7 +580,6 @@ "../../src/hooks/useParticipantSubmission.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/hooks/usePaymentOptions.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 "../../src/hooks/usePaymentOptions.ts" "@typescript-eslint/no-unsafe-type-assertion" 3 -"../../src/hooks/usePaymentOptions.ts" "react-hooks/preserve-manual-memoization" 6 "../../src/hooks/usePaymentOptions.ts" "react-hooks/refs" 1 "../../src/hooks/usePersonalPolicy.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/hooks/usePolicyData/index.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 @@ -605,7 +597,6 @@ "../../src/hooks/useReviewDuplicatesNavigation.tsx" "react-hooks/set-state-in-effect" 1 "../../src/hooks/useSearchBulkActions.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 "../../src/hooks/useSearchBulkActions.ts" "@typescript-eslint/no-unsafe-type-assertion" 9 -"../../src/hooks/useSidebarOrderedReports.tsx" "react-hooks/preserve-manual-memoization" 5 "../../src/hooks/useSidebarOrderedReports.tsx" "react-hooks/refs" 5 "../../src/hooks/useSidebarOrderedReports.tsx" "react-hooks/set-state-in-effect" 1 "../../src/hooks/useSingleExecution/index.native.ts" "react-hooks/refs" 1 @@ -694,7 +685,6 @@ "../../src/libs/actions/IOU/SplitExpenseItems.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/libs/actions/IOU/SplitTransactionUpdate.ts" "@typescript-eslint/no-unnecessary-type-assertion" 3 "../../src/libs/actions/IOU/SplitTransactionUpdate.ts" "@typescript-eslint/no-unsafe-type-assertion" 20 -"../../src/libs/actions/IOU/StartExpenseFlows.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/libs/actions/IOU/TrackExpense.ts" "@typescript-eslint/no-unnecessary-type-assertion" 4 "../../src/libs/actions/IOU/TrackExpense.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../src/libs/actions/IOU/TrackExpense.ts" "no-restricted-syntax" 2 @@ -789,7 +779,7 @@ "../../src/libs/actions/TaxRate.ts" "no-restricted-syntax" 1 "../../src/libs/actions/TeachersUnite.ts" "no-restricted-syntax" 1 "../../src/libs/actions/Transaction.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 -"../../src/libs/actions/Transaction.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/libs/actions/Transaction.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/libs/actions/Transaction.ts" "no-restricted-syntax" 2 "../../src/libs/actions/TransactionEdit.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/libs/actions/Travel.ts" "no-restricted-syntax" 1 @@ -876,7 +866,6 @@ "../../src/libs/LoginUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/libs/mapChildrenFlat.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/libs/memoize/cache/ArrayCache.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 -"../../src/libs/memoize/index.ts" "@typescript-eslint/no-unsafe-return" 3 "../../src/libs/memoize/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 8 "../../src/libs/memoize/utils.ts" "@typescript-eslint/no-unsafe-type-assertion" 7 "../../src/libs/merge/HRUtils.ts" "import/no-cycle" 1 @@ -1391,7 +1380,6 @@ "../../src/pages/Search/SearchTransactionsChangeReport.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/pages/SetDefaultWorkspacePage.tsx" "no-restricted-imports" 1 "../../src/pages/settings/AboutPage/AboutPage.tsx" "no-restricted-imports" 1 -"../../src/pages/settings/AboutPage/AboutPage.tsx" "react-hooks/preserve-manual-memoization" 9 "../../src/pages/settings/Agents/AgentsPage.tsx" "no-restricted-imports" 1 "../../src/pages/settings/Agents/DynamicAddAgentPage.tsx" "no-restricted-imports" 1 "../../src/pages/settings/Agents/DynamicEditAgentPage.tsx" "no-restricted-imports" 1 @@ -1620,7 +1608,7 @@ "../../src/pages/TransactionMerge/DynamicReceiptReviewPage.tsx" "no-restricted-imports" 1 "../../src/pages/TransactionMerge/MergeTransactionItem.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/pages/TransactionMerge/MergeTransactionsListContent.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 -"../../src/pages/TransactionMerge/MergeTransactionsListContent.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 +"../../src/pages/TransactionMerge/MergeTransactionsListContent.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/pages/Travel/CarTripDetails.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 2 "../../src/pages/Travel/DynamicDomainPermissionInfoPage.tsx" "no-restricted-imports" 1 "../../src/pages/Travel/DynamicPublicDomainErrorPage.tsx" "no-restricted-imports" 1 @@ -2136,10 +2124,8 @@ "../../tests/actions/IOU/MoneyRequestBuilderTest.ts" "no-restricted-imports" 1 "../../tests/actions/IOU/MoneyRequestSettersTest.ts" "no-restricted-imports" 1 "../../tests/actions/IOU/MoneyRequestTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 -"../../tests/actions/IOU/MoneyRequestTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../tests/actions/IOU/RequestMoneyTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/actions/IOU/RequestMoneyTest.ts" "no-restricted-imports" 1 -"../../tests/actions/IOU/SearchUpdateTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../tests/actions/IOU/SearchUpdateTest.ts" "no-restricted-imports" 1 "../../tests/actions/IOU/SplitExpenseItemsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 6 "../../tests/actions/IOU/SplitReportTotalsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 10 @@ -2181,13 +2167,11 @@ "../../tests/navigation/OneTransactionThreadRedirectHandlerOnyxTest.tsx" "@typescript-eslint/no-deprecated/originalMessage" 1 "../../tests/navigation/splitExpenseDynamicRouteTests.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 "../../tests/perf-local/OptionsListChatPreview.perf-local.ts" "@typescript-eslint/no-unnecessary-type-assertion" 5 -"../../tests/perf-local/OptionsListChatPreview.perf-local.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../tests/perf-test/DynamicReportDetailsPage.perf-test.tsx" "@typescript-eslint/no-deprecated/originalMessage" 1 "../../tests/perf-test/DynamicReportDetailsPage.perf-test.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 "../../tests/perf-test/MoneyRequestReportActionsList.perf-test.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/perf-test/MoneyRequestReportTransactionRowWrites.perf-test.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/perf-test/ReportActionCompose.perf-test.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 -"../../tests/ui/AddAgentRulePageTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../tests/ui/AgentRulesSectionTest.tsx" "@typescript-eslint/no-deprecated/MenuItemWithTopDescription" 1 "../../tests/ui/AgentRulesSectionTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 "../../tests/ui/AgentsPromoBannersTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 @@ -2196,7 +2180,6 @@ "../../tests/ui/AssignCardAssigneeStepTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/ui/BaseModalTest.tsx" "react-hooks/globals" 1 "../../tests/ui/CategorySelectorTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 8 -"../../tests/ui/ChangeReceiptBillingAccountPageTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../tests/ui/ChatActionableButtonsTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 "../../tests/ui/ChatTransactionPreviewTest.tsx" "@typescript-eslint/no-deprecated/originalMessage" 1 "../../tests/ui/ChronosTimerHeaderButton.test.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 @@ -2217,19 +2200,10 @@ "../../tests/ui/DynamicReportDetailsPageTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 "../../tests/ui/DynamicWorkspaceWorkflowsApprovalsExpensesFromPageFastEditSaveTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/ui/DynamicWorkspaceWorkflowsApprovalsExpensesFromPageTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 -"../../tests/ui/ExpensifyCodePageTest.tsx" "@typescript-eslint/no-unsafe-return" 1 "../../tests/ui/ImportedMembersPageTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 "../../tests/ui/InitialSettingsPageTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 -"../../tests/ui/IOURequestStepDistanceManualTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 -"../../tests/ui/IOURequestStepDistanceOdometerBackupTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 -"../../tests/ui/IOURequestStepDistanceOdometerDiscardGuardTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 -"../../tests/ui/IOURequestStepDistanceOdometerNextSyncTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 -"../../tests/ui/IOURequestStepDistanceOdometerSflResumeTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 -"../../tests/ui/IOURequestStepDistanceRateTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 4 "../../tests/ui/IOURequestStepDistanceTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/ui/IssueNewCardAssigneeStepTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 -"../../tests/ui/IssueNewCardAssigneeStepTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 -"../../tests/ui/IssueNewCardConfirmValidateCodePageTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../tests/ui/IssueNewCardConfirmValidateCodePageTest.tsx" "no-unsafe-optional-chaining" 1 "../../tests/ui/MoneyReportViewTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 "../../tests/ui/MoneyRequestReportActionsListRejectModalTest.tsx" "@typescript-eslint/no-deprecated/originalMessage" 2 @@ -2239,7 +2213,6 @@ "../../tests/ui/MoneyRequestReportPreview.test.tsx" "no-restricted-imports" 1 "../../tests/ui/MoneyRequestViewReceiptTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 "../../tests/ui/MoneyRequestViewTest.tsx" "@typescript-eslint/no-deprecated/originalMessage" 2 -"../../tests/ui/MyTripsPageTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../tests/ui/NumericBigNumberPadTest.tsx" "no-inner-declarations" 1 "../../tests/ui/ProfilePageTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 5 "../../tests/ui/PublicProfilePageTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 4 @@ -2249,7 +2222,6 @@ "../../tests/ui/ReportActionItemTest.tsx" "react-hooks/globals" 1 "../../tests/ui/ReportActionsListTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/ui/ReportActionsListTest.tsx" "no-unsafe-optional-chaining" 2 -"../../tests/ui/RulesNewPageDescribeRuleTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../tests/ui/SearchEditMultiplePageTest.tsx" "@typescript-eslint/no-deprecated/originalMessage" 1 "../../tests/ui/SearchMoneyRequestReportPageTest.tsx" "@typescript-eslint/no-deprecated/originalMessage" 2 "../../tests/ui/SearchMoneyRequestReportPageTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 3 @@ -2275,7 +2247,6 @@ "../../tests/ui/WorkspaceWorkflowsDEWHidePeopleTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/ui/WorkspaceWorkflowsLoadMoreTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/ui/WorkspaceWorkflowsLockedBankAccountTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 -"../../tests/ui/WorkspaceWorkflowsPayerPageTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../tests/ui/WorkspaceWorkflowsPayerRowTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/ui/WorkspaceWorkflowsSmartLimitApprovalsTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/unit/AccountSwitcherSkeletonViewTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 @@ -2286,7 +2257,6 @@ "../../tests/unit/APIWriteWhenReadyTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 3 "../../tests/unit/backgroundLocationTrackingTaskTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/unit/BankAccountUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 13 -"../../tests/unit/calculateSuperWideRHPWidthTest.ts" "@typescript-eslint/no-unsafe-return" 3 "../../tests/unit/canEditFieldOfMoneyRequestTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 "../../tests/unit/canEditFieldOfMoneyRequestTest.ts" "@typescript-eslint/no-deprecated/randomReportAction.originalMessage" 3 "../../tests/unit/canEditFieldOfMoneyRequestTest.ts" "no-restricted-imports" 1 @@ -2312,7 +2282,6 @@ "../../tests/unit/CopyPolicySettingsUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/unit/CreateReportMenuItemTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/unit/DateUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 -"../../tests/unit/DateUtilsTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 3 "../../tests/unit/DistanceRateTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 "../../tests/unit/DistanceRequestUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/unit/ExpenseDefaultRuleUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 @@ -2320,7 +2289,6 @@ "../../tests/unit/FormulaTest.ts" "no-restricted-imports" 1 "../../tests/unit/getCentralPaneReportIDTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 3 "../../tests/unit/getDefaultCardFeedTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 4 -"../../tests/unit/getReportSubmitViolationSummaryTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../tests/unit/getSendMessageListWeightTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 "../../tests/unit/GlobalReimbursementPayErrorTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 3 "../../tests/unit/goBackFromWorkspaceSettingPagesTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 5 @@ -2335,13 +2303,10 @@ "../../tests/unit/hooks/useAppReturnCount.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/unit/hooks/useAutocompleteSuggestions.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 "../../tests/unit/hooks/useBlurOnKeyboardHide.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 -"../../tests/unit/hooks/useBrokenDirectCompanyCardFeedsForAdmin.test.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../tests/unit/hooks/useChangeTransactionsReportReports.test.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 "../../tests/unit/hooks/useConfirmationSections.test.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/unit/hooks/useConfirmationValidation.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 -"../../tests/unit/hooks/useDeleteTransactionsInvoiceOfflineTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 4 "../../tests/unit/hooks/useDismissOnMoneyRequestReportRemoval.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 -"../../tests/unit/hooks/useExpenseActionsInvoiceDeleteTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../tests/unit/hooks/useExpenseSubmission.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 6 "../../tests/unit/hooks/useFeedKeysWithAssignedCards.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 5 "../../tests/unit/hooks/useFilesValidation.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 4 @@ -2379,7 +2344,6 @@ "../../tests/unit/LHNAvatarPipelineTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 7 "../../tests/unit/LhnSearchPreviewParityTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 25 "../../tests/unit/LhnSearchPreviewParityTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 -"../../tests/unit/libs/cameraCaptureTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../tests/unit/libs/receiptStorageTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/unit/libs/TransactionThreadNavigationUtilsTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 "../../tests/unit/libs/TransactionThreadNavigationUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 @@ -2392,9 +2356,7 @@ "../../tests/unit/navigateToWorkspacesPageTest.ts" "no-restricted-imports" 1 "../../tests/unit/Navigation/createRootStackNavigator/handleToggleModalWithHistoryAction.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 3 "../../tests/unit/Navigation/getIsScreenBlurredWebTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 -"../../tests/unit/Navigation/getIsScreenBlurredWebTest.ts" "@typescript-eslint/no-unsafe-return" 1 "../../tests/unit/Navigation/getLastRouteTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 -"../../tests/unit/Navigation/getTabStateWithFocusedTargetTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../tests/unit/Navigation/hasDifferentSplitScope.test.ts" "@typescript-eslint/no-unnecessary-type-assertion" 3 "../../tests/unit/Navigation/preMountBufferTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 8 "../../tests/unit/Navigation/wideTabPreMountTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 4 @@ -2406,7 +2368,6 @@ "../../tests/unit/onyxDerivedComputeSpanTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/unit/OnyxDerivedSelfHealTest.ts" "no-restricted-imports" 1 "../../tests/unit/OnyxDerivedTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 3 -"../../tests/unit/OnyxDerivedTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../tests/unit/OnyxDerivedTest.tsx" "no-restricted-imports" 1 "../../tests/unit/OptionsListUtilsTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 14 "../../tests/unit/pages/DynamicReportDetailsPage/useReportDetailsDeleteModalTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 @@ -2427,7 +2388,6 @@ "../../tests/unit/QuickActionUtilsTest.ts" "no-restricted-imports" 2 "../../tests/unit/ReceiptUploadRetryHandlerTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 "../../tests/unit/ReceiptUploadRetryHandlerTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 4 -"../../tests/unit/RenameLegacySageIntacctExportDateTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../tests/unit/ReportActionItemSingleTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/unit/ReportActionsUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 38 "../../tests/unit/ReportAlternateTextParityTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 15 @@ -2446,22 +2406,9 @@ "../../tests/unit/ReportUtilsThreadSummaryTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 "../../tests/unit/resolveTransactionThreadReportTest.ts" "@typescript-eslint/no-deprecated/originalMessage" 1 "../../tests/unit/Search/applyShiftRangeBatchToSelectionTest.ts" "@typescript-eslint/consistent-generic-constructors" 2 -"../../tests/unit/Search/ChatSearchViewTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 -"../../tests/unit/Search/ExpenseFlatSearchViewTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 -"../../tests/unit/Search/ExpenseGroupedSearchViewSplitTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 -"../../tests/unit/Search/ExpenseGroupedSearchViewTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 -"../../tests/unit/Search/ExpenseReportSearchViewTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 -"../../tests/unit/Search/FooterTotalSelectionRetentionTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../tests/unit/Search/LazyGroupSelectionTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 -"../../tests/unit/Search/LazyGroupSelectionTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 5 -"../../tests/unit/Search/SearchColumnsPageTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../tests/unit/Search/SearchQueryUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 3 "../../tests/unit/Search/SearchUIUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 4 -"../../tests/unit/Search/seedMyExpensesSearchTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 -"../../tests/unit/Search/TaskSearchViewTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 -"../../tests/unit/Search/useOptimisticSearchTrackingTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 -"../../tests/unit/Search/useSearchLoadingStateTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 -"../../tests/unit/Search/useSearchSnapshotTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../tests/unit/Search/yourSpendApplicabilityTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 "../../tests/unit/SearchActionsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 5 "../../tests/unit/SearchAutocompleteListTest.tsx" "@typescript-eslint/no-deprecated/originalMessage" 3 @@ -2498,7 +2445,6 @@ "../../tests/unit/useReceiptHoverZoomTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/unit/useReportIDToNameMapTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/unit/useScreenInitialFocusTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 -"../../tests/unit/useSearchPageSetupTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../tests/unit/useSelectionListShortcutsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/unit/useSendMessageSpanMarksTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/unit/useSidebarOrderedReportsTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 @@ -2512,7 +2458,6 @@ "../../tests/unit/WorkspacesSettingsUtilsTest.ts" "no-restricted-imports" 1 "../../tests/utils/collections/card.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/utils/createMock.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 -"../../tests/utils/createMockScreenNavigation.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../tests/utils/LHNTestUtils.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 "../../tests/utils/ReportTestUtils.ts" "@typescript-eslint/no-unnecessary-type-assertion" 2 "../../wdyr.ts" "import/no-named-as-default" 1 diff --git a/config/oxlint/plugins/core-rules.mjs b/config/oxlint/plugins/core-rules.mjs index 3dbd4f058e4d..466451ec9b6c 100644 --- a/config/oxlint/plugins/core-rules.mjs +++ b/config/oxlint/plugins/core-rules.mjs @@ -1,5 +1,5 @@ /** - * ESLint core rules that oxlint 1.89.0 has no native version of. They run inside oxlint as a JS + * ESLint core rules that oxlint has no native version of. They run inside oxlint as a JS * plugin under the `core/` prefix, using ESLint's own rule code, so each one behaves as it does * under ESLint. * diff --git a/package-lock.json b/package-lock.json index fded8bc26d9a..1c4873b9d23c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -275,10 +275,10 @@ "nitrogen": "0.37.1", "onchange": "^7.1.0", "openai": "^7.0.0", - "oxc-transform-react": "0.155.0", + "oxc-transform-react": "0.156.0", "oxfmt": "^0.55.0", - "oxlint": "1.89.0", - "oxlint-tsgolint": "7.0.2003", + "oxlint": "1.90.0", + "oxlint-tsgolint": "7.0.2005", "patch-package": "^8.1.0-canary.1", "peggy": "^4.0.3", "portfinder": "^1.0.34", @@ -13209,9 +13209,9 @@ ] }, "node_modules/@oxc-transform-react/binding-android-arm-eabi": { - "version": "0.155.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-android-arm-eabi/-/binding-android-arm-eabi-0.155.0.tgz", - "integrity": "sha512-X0gdaAcnVLdTUduUdsJ7pRPDpTwhiHRcq8wOC37HXMA1L5XaHbu/zQkeCmb84CChiIvRiQqHF6WkO8DLl62yeA==", + "version": "0.156.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-android-arm-eabi/-/binding-android-arm-eabi-0.156.0.tgz", + "integrity": "sha512-8wQ5hpMtiN6czk57sNwyi4spVd3JlcD77yssCIlnmIZyn/BWLxHKdtxQvCi04JUbIDpQqKvw9bbLeZlzIIrLuw==", "cpu": [ "arm" ], @@ -13226,9 +13226,9 @@ } }, "node_modules/@oxc-transform-react/binding-android-arm64": { - "version": "0.155.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-android-arm64/-/binding-android-arm64-0.155.0.tgz", - "integrity": "sha512-OtYUiF+jniic0JVMoyZPABnSckQUjDJwIyawDCjq8dM5f0bTDvURKgbDJrL7tR+Z86xvdxkvzwtqb0Xb+HlrBA==", + "version": "0.156.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-android-arm64/-/binding-android-arm64-0.156.0.tgz", + "integrity": "sha512-e6vRqXEnpXCRXWv8Tesv4Srpk2fspHhw3OZ+NCrJRAADtV/kLqvuHkPV5HvV/CsAmu67wb5oQyRnQOf8jsVH0w==", "cpu": [ "arm64" ], @@ -13243,9 +13243,9 @@ } }, "node_modules/@oxc-transform-react/binding-darwin-arm64": { - "version": "0.155.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-darwin-arm64/-/binding-darwin-arm64-0.155.0.tgz", - "integrity": "sha512-6EjLyB0XcCaXDWS8/q7QfMdmEGkeI11L9dtKu7Ju3Pl0dRKLcwXVdzdjGP3itzHy4RDbEAuVlCwO6lfFRpkiWg==", + "version": "0.156.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-darwin-arm64/-/binding-darwin-arm64-0.156.0.tgz", + "integrity": "sha512-GIXda96DNSjlFDZEpWvknc7uiUApx94NQ3ue7+HwTRMxn78qNNQzfuBw1duIaaxCu6xcbBjHmX0bo31Rtml6VA==", "cpu": [ "arm64" ], @@ -13260,9 +13260,9 @@ } }, "node_modules/@oxc-transform-react/binding-darwin-x64": { - "version": "0.155.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-darwin-x64/-/binding-darwin-x64-0.155.0.tgz", - "integrity": "sha512-n80yhAy3DNm3u5OqJDpNf+S7gaNpWyICSCoiTt6+oO9YC9oWkm/ywc12Qwrg7wCFtvniR64w+hWjkxKjETdscw==", + "version": "0.156.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-darwin-x64/-/binding-darwin-x64-0.156.0.tgz", + "integrity": "sha512-rPB5ZKLEPiVMr14T+Dkhi9tiEzLhtA/yN41GwfLrqsWOMxMCSjfagwm/Be/doX4bdlQ798oB8mkZqkUZzKPl9A==", "cpu": [ "x64" ], @@ -13277,9 +13277,9 @@ } }, "node_modules/@oxc-transform-react/binding-freebsd-x64": { - "version": "0.155.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-freebsd-x64/-/binding-freebsd-x64-0.155.0.tgz", - "integrity": "sha512-zZH3IX7fFrQA+zmy/amDrAe1OxqJl3vBmh+yROvsfPPaUWSn8+0Z451F9tMGhQ1iL6miBcDUHChBOVjfaJXBUg==", + "version": "0.156.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-freebsd-x64/-/binding-freebsd-x64-0.156.0.tgz", + "integrity": "sha512-MjzbjHgXD3PWdlWjXVUJsfnJ6nGfLdFvC0sCxq0iIfynR/PHzXAIrFymtvkHvfMT/lpR584gHWv/lz+XmH26Rg==", "cpu": [ "x64" ], @@ -13294,9 +13294,9 @@ } }, "node_modules/@oxc-transform-react/binding-linux-arm-gnueabihf": { - "version": "0.155.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.155.0.tgz", - "integrity": "sha512-/WnW+j/lVYwNHzaFK6uONgbC9M1S7nTWV+ONe7F9pDz3xCfvhTeWTDrqN84/vrPbxT7Xh9j3PXVnU2cw+L2NHQ==", + "version": "0.156.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.156.0.tgz", + "integrity": "sha512-n6chQcMuiC5k5W6OPHT36VxI8e9IAbH22bF1lyaKLB20lPZaPVoGU7h3lbg9jarM+US96POx3se0HtISEN90PQ==", "cpu": [ "arm" ], @@ -13311,9 +13311,9 @@ } }, "node_modules/@oxc-transform-react/binding-linux-arm-musleabihf": { - "version": "0.155.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.155.0.tgz", - "integrity": "sha512-VzH6NX11/y0S4nhvYRYWP54G7VPhTy7MoHiAp1nOsRIOaGssUATn1/fLglgz2EgJmxr5AHy1SK9dDd/PUmW5iQ==", + "version": "0.156.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.156.0.tgz", + "integrity": "sha512-9KYZbcBMkuLwB25jw4YJf1qLe3pJ7s4M+PGvqqNi9heGI9BlAjRiiCYx8dlYZ1Qe5GZS6L/5Bb5zJ6WWAYy/Ow==", "cpu": [ "arm" ], @@ -13328,9 +13328,9 @@ } }, "node_modules/@oxc-transform-react/binding-linux-arm64-gnu": { - "version": "0.155.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.155.0.tgz", - "integrity": "sha512-mLF5v1yiJghvDG5wUXWv9P0iEItM3EuFgSFfqdyY134fUsU5f7t5B7nxLr4T7DrEUWhs1+SwKNWZA+Mv4HSFiw==", + "version": "0.156.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.156.0.tgz", + "integrity": "sha512-Ti42+TdrxGmufbaYlTxOVwn+OtyRuhpUBjbA+GmbzGIE9KXGTlJ+GV2W5HFEluxVkyciPoXS1AZQXFdbJrKvww==", "cpu": [ "arm64" ], @@ -13348,9 +13348,9 @@ } }, "node_modules/@oxc-transform-react/binding-linux-arm64-musl": { - "version": "0.155.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.155.0.tgz", - "integrity": "sha512-EkBHEZKR4kDTU7+/mdleIu6VREm0zsQwDzQXAsMvbaRsmy8RkD8tOiXPNh6vyyqtahkLhEdj4vBs9c9h9iO66A==", + "version": "0.156.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.156.0.tgz", + "integrity": "sha512-5CMv/+/iJtZfhJlhVH3aLYzkUwKZpbQlofBXSAhhHqzjLrprTlgnJYis7GFf+rRZd08zjnbUbWANGVZ8jL6u2w==", "cpu": [ "arm64" ], @@ -13368,9 +13368,9 @@ } }, "node_modules/@oxc-transform-react/binding-linux-ppc64-gnu": { - "version": "0.155.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.155.0.tgz", - "integrity": "sha512-Iz26Sm7q5tO2h+da6leks3y62raTyZp32n3FX6WydxsecbFNzKpm+rKNPoti9Syd3u03rmkYlS8q7tPQMLLjUw==", + "version": "0.156.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.156.0.tgz", + "integrity": "sha512-VYu7RQBQPUHdGDgQ6IS4WjHMB69u7yyIk7xDfipHWopcLnikMc4+Gp2YxIT5ffn/a6U216x6io7oaDJxRepamA==", "cpu": [ "ppc64" ], @@ -13388,9 +13388,9 @@ } }, "node_modules/@oxc-transform-react/binding-linux-riscv64-gnu": { - "version": "0.155.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.155.0.tgz", - "integrity": "sha512-CvKMO4MNKLdmy8Q75r1rwBz+VBcSYDObCyQaJOLWAwJnsWXiV7I788N4p2iWUYPWPmfvUd6d0UrrRz5ma5CCnw==", + "version": "0.156.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.156.0.tgz", + "integrity": "sha512-AW5XhjiB/W73vwn1dyMsSb/ceR9PGpl4ZojO/X/5lpqbzIB9wGcwLPciFb3pl2yQ1tLsXfBd0V/hg/Q+FxZeag==", "cpu": [ "riscv64" ], @@ -13408,9 +13408,9 @@ } }, "node_modules/@oxc-transform-react/binding-linux-riscv64-musl": { - "version": "0.155.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.155.0.tgz", - "integrity": "sha512-OfYtP9jEMZwdnjPLVrrMvy3CQSqwdQJw6Q6YU23fObDECt1lEe2o+TJ6nFgDVAKhTl7pc9dca3vGXRgNfiBfoA==", + "version": "0.156.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.156.0.tgz", + "integrity": "sha512-bwLtY0g73vra6DFvR/Ec8lyySchTCaHDzkUuA58q2PxYY0GrhsveG+DbFPLdbEo96KiQieJVzW3FvAAFKV8vBA==", "cpu": [ "riscv64" ], @@ -13428,9 +13428,9 @@ } }, "node_modules/@oxc-transform-react/binding-linux-s390x-gnu": { - "version": "0.155.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.155.0.tgz", - "integrity": "sha512-u5USLM5psVaNX2KEzTwfcKlzgGI16EY7Iod/Z/7Fw9FwBQiN+DUyhC99NL63aJ/Qyq0N5HIG4Y95DIEekc+EfQ==", + "version": "0.156.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.156.0.tgz", + "integrity": "sha512-W2MEasN4NqYw4kkTs5MdaZ8yFPGaslQFLZbNNUygyl26z8nk1zFq5d4gZjlyCmGkw+fB6CykjTy3Cjzowgwh8g==", "cpu": [ "s390x" ], @@ -13448,9 +13448,9 @@ } }, "node_modules/@oxc-transform-react/binding-linux-x64-gnu": { - "version": "0.155.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.155.0.tgz", - "integrity": "sha512-Kbddg5Eu/B+l6xW0CS5kaNNRK79IWu7o4NsDkn8SMz7k0N8CHV5watP0pscPIUuxcknBZkQxGqQ+b+qV7B18vA==", + "version": "0.156.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.156.0.tgz", + "integrity": "sha512-+CfzH5OiKZjz/2TqVcgMhYaN0ruogou3T1+nycmG0WjptmDJ7BYHwHIYjfRaAI87beps4MgXltgMFLGkm2N/JQ==", "cpu": [ "x64" ], @@ -13468,9 +13468,9 @@ } }, "node_modules/@oxc-transform-react/binding-linux-x64-musl": { - "version": "0.155.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-x64-musl/-/binding-linux-x64-musl-0.155.0.tgz", - "integrity": "sha512-hFZ69Mh6rZ1XeqSv4njgAB5WgKGBSTgH596EHusayzH+jtgczsMPK81k4SWlguKxrrzIqezLC8pyzhgruaNQGw==", + "version": "0.156.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-linux-x64-musl/-/binding-linux-x64-musl-0.156.0.tgz", + "integrity": "sha512-3/tonoHWE0nqzkB9+4+X9ipAU325Yd8wCDUrqh0QpcEvs/RTl5VZ3i+PboYRmnu4jPDssh4Lg4pFsgkgBN89fw==", "cpu": [ "x64" ], @@ -13488,9 +13488,9 @@ } }, "node_modules/@oxc-transform-react/binding-openharmony-arm64": { - "version": "0.155.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-openharmony-arm64/-/binding-openharmony-arm64-0.155.0.tgz", - "integrity": "sha512-XikOLTZAchqLWNep6ptHZnA47EGlV0s7zpCA7iPRuiM/V9xfgqBbolryFu9/lNjoN6twmt8k0aRuW0frxSTVNQ==", + "version": "0.156.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-openharmony-arm64/-/binding-openharmony-arm64-0.156.0.tgz", + "integrity": "sha512-yRo3fXeIJqTKs5AUUQrt4uOGGmlz1PqrgF2VrehrJFNBm/PHgKjXu7jjAPhu4c3z4UfFyVBHg4FArw3bMDQcLA==", "cpu": [ "arm64" ], @@ -13505,9 +13505,9 @@ } }, "node_modules/@oxc-transform-react/binding-win32-arm64-msvc": { - "version": "0.155.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.155.0.tgz", - "integrity": "sha512-3aJKnNR9dBcSTfrrB3PxaYP5N1Myua8DZSHhe4SEp8M/SusvxdaK6bf9h6h801UsQB66S75fk4iTyEShLHZztA==", + "version": "0.156.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.156.0.tgz", + "integrity": "sha512-hzNsvmSRyd7gWpzDxL7xtn3nzP2os+2Qhk/pFgrUMt/atd2Lo65+SDt1SL2Izc3t2sgbs1vCSW1udk6FBRlI2g==", "cpu": [ "arm64" ], @@ -13522,9 +13522,9 @@ } }, "node_modules/@oxc-transform-react/binding-win32-ia32-msvc": { - "version": "0.155.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.155.0.tgz", - "integrity": "sha512-i2ZSZ79dSgVvC+Ksxju3/4p9+6vXzbeY7iOoBreL4mNOCjQziYChdv5Z921V9AZ4E4LVnD/NoHpez6M8T0Ga3A==", + "version": "0.156.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.156.0.tgz", + "integrity": "sha512-DqX6XJMWxx3McbORNbD0ADoaWb6S4iMW0rrtIi8iQt2GXloHYUrYh4jqj1cbZNFXZtTJQJSB+vVRL1UVIiE7gg==", "cpu": [ "ia32" ], @@ -13539,9 +13539,9 @@ } }, "node_modules/@oxc-transform-react/binding-win32-x64-msvc": { - "version": "0.155.0", - "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.155.0.tgz", - "integrity": "sha512-lrPIzcUHw/hB/LuT/G639+IrPCvvMlthF3xCE9grz1IbsSk+22sb5zyCxTvmpBxZR7/E1ckwZdnw7AmpAd+7Jg==", + "version": "0.156.0", + "resolved": "https://registry.npmjs.org/@oxc-transform-react/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.156.0.tgz", + "integrity": "sha512-q9prdGO0cgS+fFCsHmW6O8ckf6JS0u3D5DbFbg0Vxsft6cwi1aWI+bnFpi6MaiN3hn3zJF763K8DGl9qOQZ9TA==", "cpu": [ "x64" ], @@ -13879,9 +13879,9 @@ } }, "node_modules/@oxlint-tsgolint/darwin-arm64": { - "version": "7.0.2003", - "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/darwin-arm64/-/darwin-arm64-7.0.2003.tgz", - "integrity": "sha512-TgV33rXr6ueXBwvc+0nssUkTBSXHJxv77I8p4RCjDjCnvexHtmoPiudVRDfj3S3puMDdeQdHW6jdUgUPy3nr/w==", + "version": "7.0.2005", + "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/darwin-arm64/-/darwin-arm64-7.0.2005.tgz", + "integrity": "sha512-VqDZxcZVjlonNLuuvraVNgDQt+1ndwa4yJo8OqsK4HYh/61ba9IlMStCVfi/aaIqRoDhNbAKC8gdcTrXqnb+Ug==", "cpu": [ "arm64" ], @@ -13893,9 +13893,9 @@ ] }, "node_modules/@oxlint-tsgolint/darwin-x64": { - "version": "7.0.2003", - "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/darwin-x64/-/darwin-x64-7.0.2003.tgz", - "integrity": "sha512-hY3FMAjIaPDdK3FNxyRXRfHPOFeoBn6dmnLyKZgQ2IbTTD1adXhl6PfCIqHhCPvurigv7nf7mYuWZZ19MmJzmg==", + "version": "7.0.2005", + "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/darwin-x64/-/darwin-x64-7.0.2005.tgz", + "integrity": "sha512-4kUfLRNYoVpv0Tm3jPYrvxyfOc/LmdvFC3pKZ4tL6K7lA7K/VS81SAtmxRzbYXYSyb3SBWhwsuX+df4neqzxxA==", "cpu": [ "x64" ], @@ -13907,9 +13907,9 @@ ] }, "node_modules/@oxlint-tsgolint/linux-arm64": { - "version": "7.0.2003", - "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/linux-arm64/-/linux-arm64-7.0.2003.tgz", - "integrity": "sha512-eAET4JpyfBbg8SO0K74o4R55tEjVC292pIyxGOw2XGf8x/HrPNyxwQ478jMYOM54FIVOHc8sJ6VRHxluy6lucw==", + "version": "7.0.2005", + "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/linux-arm64/-/linux-arm64-7.0.2005.tgz", + "integrity": "sha512-rhUNcPsBlKvhXtrbhbBUidtRBRdJ8fbEHN9H5Q5N59gby18ruGuDA7KUxl7oRnUzp797uykYQhkBDm+0KpfK/Q==", "cpu": [ "arm64" ], @@ -13921,9 +13921,9 @@ ] }, "node_modules/@oxlint-tsgolint/linux-x64": { - "version": "7.0.2003", - "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/linux-x64/-/linux-x64-7.0.2003.tgz", - "integrity": "sha512-GXdyO/XyqDJ3s/llR/oOktLsYNjZtWSQBy0JMc+/0gsNPvTcseKPhn9c6KcFyWmnr6H4vljYALbujonqSzzEGw==", + "version": "7.0.2005", + "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/linux-x64/-/linux-x64-7.0.2005.tgz", + "integrity": "sha512-tKs9yinDQZOHygM0e8EJnjTU3mzJtdfkOZP6Udu82iLMePUSHOhTJ9zzU/mSd1hQxURI4LTRRiFhfzC+PzwdNA==", "cpu": [ "x64" ], @@ -13935,9 +13935,9 @@ ] }, "node_modules/@oxlint-tsgolint/win32-arm64": { - "version": "7.0.2003", - "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/win32-arm64/-/win32-arm64-7.0.2003.tgz", - "integrity": "sha512-TWauXnPfet0VgpmrstoAK58eJ4gbuwPUuUTQmYQAzE/RG1CpnxXtrKUJULf6lyerPE4KN3gM+DflIA1hOH+38Q==", + "version": "7.0.2005", + "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/win32-arm64/-/win32-arm64-7.0.2005.tgz", + "integrity": "sha512-VYPnu1gjfbSv+ROhuyNfMs382D4zQU2ZRc8Uc3nuEZo1k2kf+1KfTrg4hrlWh73h9GI+dUWyo+DYCQzUBlPwcQ==", "cpu": [ "arm64" ], @@ -13949,9 +13949,9 @@ ] }, "node_modules/@oxlint-tsgolint/win32-x64": { - "version": "7.0.2003", - "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/win32-x64/-/win32-x64-7.0.2003.tgz", - "integrity": "sha512-DoRmfe7j8VqNlukp8liRVW45GQDhzRccNenjD/pdzelgtffW47pCMd1xbJLkPaPbKnTwID3onn3VZlL7JbebEA==", + "version": "7.0.2005", + "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/win32-x64/-/win32-x64-7.0.2005.tgz", + "integrity": "sha512-ekU89cTGy9fpQzuvxIsFHrnoP8XoFjGIRgPEW6eqgY2NYjGy9pwpjF79o04WXRMJFKRrcaVqkZXUUBdLsqpQDw==", "cpu": [ "x64" ], @@ -13963,9 +13963,9 @@ ] }, "node_modules/@oxlint/binding-android-arm-eabi": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.89.0.tgz", - "integrity": "sha512-Pflpba5F3gRAQ12sWglRxvWTHEdAusNDvmi4Y5N1T5SY1AGkM7AixOnET8qkrnLJ/F32qAlNokO6eCknT8tmnw==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.90.0.tgz", + "integrity": "sha512-sgfalb1HuxYjiAa4wESZ626z1FGusqNcXYHyB5oIOEprS2sbPg0UYEfo83uF1GNG70DIfZxHkXuE6KFrlSdn8Q==", "cpu": [ "arm" ], @@ -13980,9 +13980,9 @@ } }, "node_modules/@oxlint/binding-android-arm64": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.89.0.tgz", - "integrity": "sha512-dYmyD1x7LaPlb79vZWUcpTShk8dI9z1I+IubtqrBxWNJaDhQdPHwcsvg7q5StTU4xWyd9AwPfLaTYFZY8NZgtQ==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.90.0.tgz", + "integrity": "sha512-RcKhz1ETMq+fazE9TKS2+5NIRGwigm/nW8egqF0LI+A8Yw2iW5r0E/C3XAPRiwIm/vcPiutIRIa6IQCIKhZsTQ==", "cpu": [ "arm64" ], @@ -13997,9 +13997,9 @@ } }, "node_modules/@oxlint/binding-darwin-arm64": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.89.0.tgz", - "integrity": "sha512-Z/oxOPiIqiS7fItRdxaaOEC+6GD3OpGi0IxoR0dMefzQHolsKLzScw2DNcu/5gUPok3tv+G9mslvYB5bo3i0qg==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.90.0.tgz", + "integrity": "sha512-PSrxMvP6mx+p1karZcUxLdy9lpaP+JWQPti4al9FgTaPhxE+pT/BjhGExloVVSWgZgIfOlLOjVyyq8BXlodhLQ==", "cpu": [ "arm64" ], @@ -14014,9 +14014,9 @@ } }, "node_modules/@oxlint/binding-darwin-x64": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.89.0.tgz", - "integrity": "sha512-eAjTkHSjAxrdCPJxO6y5sPQL5pYLKb2kvxZz9UU7tXnOZk9I/HLtkvx9S7GHY883k+AmL/eC7mqFt+w7WCwk0w==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.90.0.tgz", + "integrity": "sha512-OLbU9qsrV2Q8QxT2OEzOPdO8zCCpyI8qB+hDI8yr6RsYELtkf0FAZhJdfjM/RnLHKzt44HWwAPnk92vBtKofvw==", "cpu": [ "x64" ], @@ -14031,9 +14031,9 @@ } }, "node_modules/@oxlint/binding-freebsd-x64": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.89.0.tgz", - "integrity": "sha512-0lNCA9Tl1n1FKpqy2/9ckp0kIuSrtJ/l9bMRgaEhOtM7TsOHshFuwOcvW8oA9teXLMOJT61LjEKWiaHDFGxF/w==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.90.0.tgz", + "integrity": "sha512-fBA4pWyVDARmNucQqpyFuPC49xYf1r+sX7dxPL5fNPQrSMLum39Sn56tZ9JPleWC7FzLPwSQ7plYYfirYBHF8A==", "cpu": [ "x64" ], @@ -14048,9 +14048,9 @@ } }, "node_modules/@oxlint/binding-linux-arm-gnueabihf": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.89.0.tgz", - "integrity": "sha512-OTFEvi9W+JczaZD1Caj17SGCoW8H7TE+fvx0D7ATEWa1Lohn91UMxFQ6r145SiGy/yCxeIwptMys/RAM7gSVSQ==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.90.0.tgz", + "integrity": "sha512-zhxH78eP2nR6/pGdhBc7MIfjhhcgATeu8fkmkFJbuzfk6Cj0FzBxyEhL85yDqrCybjx+G2YDzZGsxIO9kvO4Iw==", "cpu": [ "arm" ], @@ -14065,9 +14065,9 @@ } }, "node_modules/@oxlint/binding-linux-arm-musleabihf": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.89.0.tgz", - "integrity": "sha512-fRxgNHvBLswj6MrhW2uBq5UDhPRQlCbHeq/Nf8yuG9zOxhDsXqX/mi2PX1axgsQxMm88el7HLiJAzglAnflYxw==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.90.0.tgz", + "integrity": "sha512-MvYmpHyJqUJjEvkZbJHdwfdL45cGn8EVBkYmoXFiaYFW9suoD3dbJl5mBdpy0DVgdjlyddmldbRtk3cCgETadQ==", "cpu": [ "arm" ], @@ -14082,9 +14082,9 @@ } }, "node_modules/@oxlint/binding-linux-arm64-gnu": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.89.0.tgz", - "integrity": "sha512-v9ENzHvwrQoYSWTtviH1pOYc7R9UovDxVofnck50cNmPihRRTcrg5X0UlRbLOZ7IVtxPOlh2hb7kDO+FpE8SfQ==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.90.0.tgz", + "integrity": "sha512-L13Sy+Kg0nZhwTsToniqjUFPA2TuM6962qCZ5Wp5KeB517Tw7ApdLTTtlb+bF86fcZZa2EtvCVvAJxk4Dya/MQ==", "cpu": [ "arm64" ], @@ -14102,9 +14102,9 @@ } }, "node_modules/@oxlint/binding-linux-arm64-musl": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.89.0.tgz", - "integrity": "sha512-vfffe8mQ484AzVadlUOFpgL+JDwhvs9hrzxTgQP1FVfi4ZEUUYeffXlzC9dFV7Dgnu5s/KrNKvfmG6HxBd/ISQ==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.90.0.tgz", + "integrity": "sha512-wFDdZWGpzIPFdJFjSLWnKB0TGzFriTKUIrl04iMqMyM9BBIJTuerfCHYa0WpDSjr5x3lcW/CHLyacSt886WpGg==", "cpu": [ "arm64" ], @@ -14122,9 +14122,9 @@ } }, "node_modules/@oxlint/binding-linux-ppc64-gnu": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.89.0.tgz", - "integrity": "sha512-Buonlp4hVS7RGNP5M7bPpGNQrenyzA2C64JcO/oz9C1c6dGOXJ7VEAnaiAMY7voou0JYyCJdvDVlFQc5DZX3DQ==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.90.0.tgz", + "integrity": "sha512-FRywfn+MoKn8xpBSzgNwNVliSKRI2QXkxi/iF7o6FEQs8Bh8z2RpVTaFAcO3pchXjvB2eGNFIoLLQABIQ+u7VA==", "cpu": [ "ppc64" ], @@ -14142,9 +14142,9 @@ } }, "node_modules/@oxlint/binding-linux-riscv64-gnu": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.89.0.tgz", - "integrity": "sha512-1eVwJJrJ3lYIPT+cm7BcBBZZXY6IQZP/oyonaruTor/MrVyWOpvnwt1oD5CLdsHg5aVBIxXodQSvt+1gK6ck5w==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.90.0.tgz", + "integrity": "sha512-IS2PaUgpc/qeLADTyhUNmdx3x4ijs9pjJdPKBYHaT+mAE7k3FsjnHPTjhpsak1b++h6CIPhv6WX9Z+y7Pw0voQ==", "cpu": [ "riscv64" ], @@ -14162,9 +14162,9 @@ } }, "node_modules/@oxlint/binding-linux-riscv64-musl": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.89.0.tgz", - "integrity": "sha512-fRQQsKUGShdEL5Fl9o9uhj3grkZbeXKLv0icQ5tspUCNYSDy8wzzEcP1WC2bKdTQ0rSszbKlO3iiHORxV22DLg==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.90.0.tgz", + "integrity": "sha512-s7JFKRkGS8O7i2ZVODfuO++NC7sH6HDqpMn8lYxw91w0SXJOxnAVjQ8utvmDwLMMT0jaSkuOKuUYJDGDtu4sHQ==", "cpu": [ "riscv64" ], @@ -14182,9 +14182,9 @@ } }, "node_modules/@oxlint/binding-linux-s390x-gnu": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.89.0.tgz", - "integrity": "sha512-dEtYjIy6+fHcjKklsmtmMkZRhwHr2UbzjmpvuPtQYSeN9awQ2hx5JLms5Wvri+8P/NZtD8v5hfgHALUql+/Ycg==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.90.0.tgz", + "integrity": "sha512-b0GxrOQr5VONCkd7KLSqmP6b6nqZuTpuS8QPQHzltmqdXP5bY0Jl0CZ0QmlCTrS3ExaLYbioFYOARzNbYvcApQ==", "cpu": [ "s390x" ], @@ -14202,9 +14202,9 @@ } }, "node_modules/@oxlint/binding-linux-x64-gnu": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.89.0.tgz", - "integrity": "sha512-hqjwIj5KOYM8Wjef/p7IruNi1iByLhSzbMBk62y22P/E8q7Jj0Pp+DeUTKTRYyZYCDtU1wEGlnWOnttHbyNIww==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.90.0.tgz", + "integrity": "sha512-iaJeShJtgUp8rZbt9YUdTAGOpmX5KogCLba17Jp1tWdJ3F4M3D2g2TKUyI1FMTHxKH9NNHuQcuC2kZRg6IV7yQ==", "cpu": [ "x64" ], @@ -14222,9 +14222,9 @@ } }, "node_modules/@oxlint/binding-linux-x64-musl": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.89.0.tgz", - "integrity": "sha512-iAQN3e6o9UgUCcdGwqRx/jYcKbG3HK7ur3rH6lTqVcQeNGZ85AeWIg6cgn/edwFQ0ofEZZItMwkzyCVTahnvEA==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.90.0.tgz", + "integrity": "sha512-/aZoXnL5WBLCXuzfHqvEFXcm4iwYmr/cYByHDTXzLFzJdE5DC7bm6phpWqHv6BeqpVgY9IpBAxGDv2WEkl5JCg==", "cpu": [ "x64" ], @@ -14242,9 +14242,9 @@ } }, "node_modules/@oxlint/binding-openharmony-arm64": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.89.0.tgz", - "integrity": "sha512-MNxjEByz3N/7Q4qFQuPWvc14TpN9LoD3pECEDsJTZbP/wyB7U+hduUXEl7QAbbgVezFwsb9sWtNUO6XVBndOhg==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.90.0.tgz", + "integrity": "sha512-SMhk7ozwoxYDKfYqGZCQEfULOKDneF9uhaoIf2neoDLWvNtjY9yVeL7YHmE5L9WdKvMpWNwwZxhenxBu6AgfOg==", "cpu": [ "arm64" ], @@ -14259,9 +14259,9 @@ } }, "node_modules/@oxlint/binding-win32-arm64-msvc": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.89.0.tgz", - "integrity": "sha512-xxe+i9wfuFso/VnoL4mfj0UlzE+cfCFiUB06vkXCMyQdgVVmItF5FfAF4tATAktSU6uHruWR+FjqvAT9cHZZzg==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.90.0.tgz", + "integrity": "sha512-KBvR3A9fO7fCi2vCP8ifWvlia7tGEQV9NaYh+IGwa/7Groy48Mt/UaYqMK9/zOtAWl20BUCf6RPqzJ32fRw33Q==", "cpu": [ "arm64" ], @@ -14276,9 +14276,9 @@ } }, "node_modules/@oxlint/binding-win32-ia32-msvc": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.89.0.tgz", - "integrity": "sha512-saFy07cO94E664glwgLmKLI40HgTL1PY86y4T6+ud4niX4H0Kdqjcmqmjps7mQNdBytV5guGbXDdyQtRfeLbLA==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.90.0.tgz", + "integrity": "sha512-+x42rDHQGbTCuT3EzpWAzhP3NoVPE/cJsG4gnrakOSg0olV+GCdMpPU23Nvrl3zZMnXxFZdTtDv4D3SuaA5ZeQ==", "cpu": [ "ia32" ], @@ -14293,9 +14293,9 @@ } }, "node_modules/@oxlint/binding-win32-x64-msvc": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.89.0.tgz", - "integrity": "sha512-hLFJ/o0f/dTJh/EW0vmcbREYSQO9tYgfiAAlsOkiLFiJuRRWvYtFELUXN5o2ijpSo/tlO1n+YlWN58tZwPG18A==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.90.0.tgz", + "integrity": "sha512-30JiaFEBUB9peEOcvVM8ggo88SoN/IdSr9DW505JdBji/R6dM66gKEiG65hw6qcaF2f4mO0cEJ+RwRZprmun9g==", "cpu": [ "x64" ], @@ -35779,9 +35779,9 @@ } }, "node_modules/oxc-transform-react": { - "version": "0.155.0", - "resolved": "https://registry.npmjs.org/oxc-transform-react/-/oxc-transform-react-0.155.0.tgz", - "integrity": "sha512-JsZb0yduU1JdSj1RrNov2Ysio68LW9VKa10vkAJehy3NsE6UZrfcjmTLrStBRVF40YKxN30I+Cq8zJxIXzLqHQ==", + "version": "0.156.0", + "resolved": "https://registry.npmjs.org/oxc-transform-react/-/oxc-transform-react-0.156.0.tgz", + "integrity": "sha512-BQQ/DKTy80gwDx+JHFc/gPnt2BXY0yqveRQo9IjWD/H7OpUkG5qQxwrQmQwVQzHfJnrCbUL518aeA9rvar6TDg==", "dev": true, "license": "MIT", "engines": { @@ -35791,25 +35791,25 @@ "url": "https://github.com/sponsors/oxc-project" }, "optionalDependencies": { - "@oxc-transform-react/binding-android-arm-eabi": "0.155.0", - "@oxc-transform-react/binding-android-arm64": "0.155.0", - "@oxc-transform-react/binding-darwin-arm64": "0.155.0", - "@oxc-transform-react/binding-darwin-x64": "0.155.0", - "@oxc-transform-react/binding-freebsd-x64": "0.155.0", - "@oxc-transform-react/binding-linux-arm-gnueabihf": "0.155.0", - "@oxc-transform-react/binding-linux-arm-musleabihf": "0.155.0", - "@oxc-transform-react/binding-linux-arm64-gnu": "0.155.0", - "@oxc-transform-react/binding-linux-arm64-musl": "0.155.0", - "@oxc-transform-react/binding-linux-ppc64-gnu": "0.155.0", - "@oxc-transform-react/binding-linux-riscv64-gnu": "0.155.0", - "@oxc-transform-react/binding-linux-riscv64-musl": "0.155.0", - "@oxc-transform-react/binding-linux-s390x-gnu": "0.155.0", - "@oxc-transform-react/binding-linux-x64-gnu": "0.155.0", - "@oxc-transform-react/binding-linux-x64-musl": "0.155.0", - "@oxc-transform-react/binding-openharmony-arm64": "0.155.0", - "@oxc-transform-react/binding-win32-arm64-msvc": "0.155.0", - "@oxc-transform-react/binding-win32-ia32-msvc": "0.155.0", - "@oxc-transform-react/binding-win32-x64-msvc": "0.155.0" + "@oxc-transform-react/binding-android-arm-eabi": "0.156.0", + "@oxc-transform-react/binding-android-arm64": "0.156.0", + "@oxc-transform-react/binding-darwin-arm64": "0.156.0", + "@oxc-transform-react/binding-darwin-x64": "0.156.0", + "@oxc-transform-react/binding-freebsd-x64": "0.156.0", + "@oxc-transform-react/binding-linux-arm-gnueabihf": "0.156.0", + "@oxc-transform-react/binding-linux-arm-musleabihf": "0.156.0", + "@oxc-transform-react/binding-linux-arm64-gnu": "0.156.0", + "@oxc-transform-react/binding-linux-arm64-musl": "0.156.0", + "@oxc-transform-react/binding-linux-ppc64-gnu": "0.156.0", + "@oxc-transform-react/binding-linux-riscv64-gnu": "0.156.0", + "@oxc-transform-react/binding-linux-riscv64-musl": "0.156.0", + "@oxc-transform-react/binding-linux-s390x-gnu": "0.156.0", + "@oxc-transform-react/binding-linux-x64-gnu": "0.156.0", + "@oxc-transform-react/binding-linux-x64-musl": "0.156.0", + "@oxc-transform-react/binding-openharmony-arm64": "0.156.0", + "@oxc-transform-react/binding-win32-arm64-msvc": "0.156.0", + "@oxc-transform-react/binding-win32-ia32-msvc": "0.156.0", + "@oxc-transform-react/binding-win32-x64-msvc": "0.156.0" } }, "node_modules/oxfmt": { @@ -35865,9 +35865,9 @@ } }, "node_modules/oxlint": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.89.0.tgz", - "integrity": "sha512-a25hkfo61okGPvJz432fRIPoxlKtNnOVy9w4rzrmGgNm4iVTPwpSQlmdqNWnp6p9QyyPVfAZRgnf5XvSBZFk/A==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.90.0.tgz", + "integrity": "sha512-RkTN9EmgIUpLuhNoLhO3OliMGJlvb3Dk7XVjBZnfoMCHrVpuz5ESd97klfiyYeV7XfA2lNLnDCPMkUI+Qw5How==", "dev": true, "license": "MIT", "bin": { @@ -35880,28 +35880,28 @@ "url": "https://github.com/sponsors/oxc-project" }, "optionalDependencies": { - "@oxlint/binding-android-arm-eabi": "1.89.0", - "@oxlint/binding-android-arm64": "1.89.0", - "@oxlint/binding-darwin-arm64": "1.89.0", - "@oxlint/binding-darwin-x64": "1.89.0", - "@oxlint/binding-freebsd-x64": "1.89.0", - "@oxlint/binding-linux-arm-gnueabihf": "1.89.0", - "@oxlint/binding-linux-arm-musleabihf": "1.89.0", - "@oxlint/binding-linux-arm64-gnu": "1.89.0", - "@oxlint/binding-linux-arm64-musl": "1.89.0", - "@oxlint/binding-linux-ppc64-gnu": "1.89.0", - "@oxlint/binding-linux-riscv64-gnu": "1.89.0", - "@oxlint/binding-linux-riscv64-musl": "1.89.0", - "@oxlint/binding-linux-s390x-gnu": "1.89.0", - "@oxlint/binding-linux-x64-gnu": "1.89.0", - "@oxlint/binding-linux-x64-musl": "1.89.0", - "@oxlint/binding-openharmony-arm64": "1.89.0", - "@oxlint/binding-win32-arm64-msvc": "1.89.0", - "@oxlint/binding-win32-ia32-msvc": "1.89.0", - "@oxlint/binding-win32-x64-msvc": "1.89.0" - }, - "peerDependencies": { - "oxlint-tsgolint": ">=7.0.2003", + "@oxlint/binding-android-arm-eabi": "1.90.0", + "@oxlint/binding-android-arm64": "1.90.0", + "@oxlint/binding-darwin-arm64": "1.90.0", + "@oxlint/binding-darwin-x64": "1.90.0", + "@oxlint/binding-freebsd-x64": "1.90.0", + "@oxlint/binding-linux-arm-gnueabihf": "1.90.0", + "@oxlint/binding-linux-arm-musleabihf": "1.90.0", + "@oxlint/binding-linux-arm64-gnu": "1.90.0", + "@oxlint/binding-linux-arm64-musl": "1.90.0", + "@oxlint/binding-linux-ppc64-gnu": "1.90.0", + "@oxlint/binding-linux-riscv64-gnu": "1.90.0", + "@oxlint/binding-linux-riscv64-musl": "1.90.0", + "@oxlint/binding-linux-s390x-gnu": "1.90.0", + "@oxlint/binding-linux-x64-gnu": "1.90.0", + "@oxlint/binding-linux-x64-musl": "1.90.0", + "@oxlint/binding-openharmony-arm64": "1.90.0", + "@oxlint/binding-win32-arm64-msvc": "1.90.0", + "@oxlint/binding-win32-ia32-msvc": "1.90.0", + "@oxlint/binding-win32-x64-msvc": "1.90.0" + }, + "peerDependencies": { + "oxlint-tsgolint": ">=7.0.2005", "vite-plus": "*" }, "peerDependenciesMeta": { @@ -35914,21 +35914,21 @@ } }, "node_modules/oxlint-tsgolint": { - "version": "7.0.2003", - "resolved": "https://registry.npmjs.org/oxlint-tsgolint/-/oxlint-tsgolint-7.0.2003.tgz", - "integrity": "sha512-VnK4zlqgmgq/7ZcjzCk/WpN8kKFsYGcB85Io9qT3wL4K8Un3RKmJkp793crNETieMusPMKOA4a+Mw2wbteI6TQ==", + "version": "7.0.2005", + "resolved": "https://registry.npmjs.org/oxlint-tsgolint/-/oxlint-tsgolint-7.0.2005.tgz", + "integrity": "sha512-6ccPGu4u4J9bwYPHXBFPee3RZrX3BboRdU7k14zRDDB1lvN3O7J/vPRFdaEfa1+CWx4xnQQOp4ctoaF66oO0uw==", "dev": true, "license": "MIT", "bin": { "tsgolint": "bin/tsgolint.js" }, "optionalDependencies": { - "@oxlint-tsgolint/darwin-arm64": "7.0.2003", - "@oxlint-tsgolint/darwin-x64": "7.0.2003", - "@oxlint-tsgolint/linux-arm64": "7.0.2003", - "@oxlint-tsgolint/linux-x64": "7.0.2003", - "@oxlint-tsgolint/win32-arm64": "7.0.2003", - "@oxlint-tsgolint/win32-x64": "7.0.2003" + "@oxlint-tsgolint/darwin-arm64": "7.0.2005", + "@oxlint-tsgolint/darwin-x64": "7.0.2005", + "@oxlint-tsgolint/linux-arm64": "7.0.2005", + "@oxlint-tsgolint/linux-x64": "7.0.2005", + "@oxlint-tsgolint/win32-arm64": "7.0.2005", + "@oxlint-tsgolint/win32-x64": "7.0.2005" } }, "node_modules/p-limit": { diff --git a/package.json b/package.json index 0e4adb3c386c..4f118778e488 100644 --- a/package.json +++ b/package.json @@ -351,10 +351,10 @@ "nitrogen": "0.37.1", "onchange": "^7.1.0", "openai": "^7.0.0", - "oxc-transform-react": "0.155.0", + "oxc-transform-react": "0.156.0", "oxfmt": "^0.55.0", - "oxlint": "1.89.0", - "oxlint-tsgolint": "7.0.2003", + "oxlint": "1.90.0", + "oxlint-tsgolint": "7.0.2005", "patch-package": "^8.1.0-canary.1", "peggy": "^4.0.3", "portfinder": "^1.0.34", diff --git a/tests/tooling/lintPipeline.test.ts b/tests/tooling/lintPipeline.test.ts index 47e96beb4a98..1ff6e07d8e72 100644 --- a/tests/tooling/lintPipeline.test.ts +++ b/tests/tooling/lintPipeline.test.ts @@ -397,7 +397,7 @@ describe('parseOxlintStdout', () => { }); it('treats a crashing JS plugin as fatal, not a finding', () => { - // Given the codeless, label-less diagnostic oxlint 1.89.0 emits when a JS plugin rule throws + // Given the codeless, label-less diagnostic oxlint emits when a JS plugin rule throws const crash = oxlintDiagnostic({code: undefined, labels: [], message: 'Error running JS plugin.\nFile path: /repo/src/a.ts\nError: plugin blew up'}); // When the report is parsed