diff --git a/.github/workflows/oxlint.yml b/.github/workflows/oxlint.yml new file mode 100644 index 000000000000..2de009114c09 --- /dev/null +++ b/.github/workflows/oxlint.yml @@ -0,0 +1,72 @@ +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 splits them into 4 shards on this 8 vCPU runner. + # 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 + + - 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" + 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 + with: + OP_VAULT: ${{ vars.OP_VAULT }} + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + SETUP_AS_APP: false + + - 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-update oxlint-seatbelt baseline" + git push origin main diff --git a/.github/workflows/preDeploy.yml b/.github/workflows/preDeploy.yml index 0c09e86b3098..2846145b97db 100644 --- a/.github/workflows/preDeploy.yml +++ b/.github/workflows/preDeploy.yml @@ -14,6 +14,11 @@ jobs: uses: ./.github/workflows/lint.yml secrets: inherit + # 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 + oxfmt: uses: ./.github/workflows/oxfmt.yml diff --git a/.gitignore b/.gitignore index 8c6d0691fdb9..bfd9e70329b1 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 + +# 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 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/eslintDirectives.mjs b/config/oxlint/eslintDirectives.mjs new file mode 100644 index 000000000000..e0f5de6a59ac --- /dev/null +++ b/config/oxlint/eslintDirectives.mjs @@ -0,0 +1,176 @@ +/** + * 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']; +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 {{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) { + 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]}; +} + +/** + * 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 {Array[0]>} comments + * @returns {{lines: Map>, blocks: Array<{line: number, column: number, ruleId: string, disable: boolean}>}} + */ +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; + } + // 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` + // to the line after the comment ends. + 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, column: comment.loc.start.column ?? 0, ruleId, disable: parsed.kind === 'eslint-disable'}); + } + } + } + + return {lines, blocks}; +} + +/** + * Every wrapped rule checks the same file's comments, so they are parsed once per file. + * @param {string | undefined} filename + * @param {Array[0]>} comments + * @returns {ReturnType} + */ +function directivesFor(filename, comments) { + const key = filename ?? ''; + if (!cache.has(key)) { + cache.set(key, parseDirectives(comments)); + } + return cache.get(key); +} + +/** + * 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; + } + 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) { + const isAfterReport = block.line > line || (block.line === line && block.column > column); + if (isAfterReport || (block.ruleId !== ALL_RULES && block.ruleId !== eslintRuleId)) { + continue; + } + disabled = block.disable; + } + 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]; + const point = candidates.find((candidate) => typeof candidate?.line === 'number'); + 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, + create(context) { + const filteredContext = Object.create(context, { + report: { + // Keeps every argument: ESLint also accepts the legacy `report(node, message)` form. + value(...args) { + 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, position)) { + return; + } + } + return context.report(...args); + }, + }, + }); + return rule.create(filteredContext); + }, + }; +} + +/** + * 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))])); +} + +export {withEslintDirectiveIDs, withEslintDirectiveIDsFor}; diff --git a/config/oxlint/noDeprecatedSelfClosingJsx.mjs b/config/oxlint/noDeprecatedSelfClosingJsx.mjs new file mode 100644 index 000000000000..cd1dd08bf5a1 --- /dev/null +++ b/config/oxlint/noDeprecatedSelfClosingJsx.mjs @@ -0,0 +1,197 @@ +/** + * 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'; +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') + .replaceAll(/("(?:\\.|[^"\\])*")|\/\/[^\n]*|\/\*[\s\S]*?\*\//g, (match, string) => string ?? '') + .replaceAll(/,(\s*[}\]])/g, '$1'); + return JSON.parse(text); +} + +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 ?? {}; + 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; +} + +/** + * 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('.')) { + 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, + }, + ], +}; + +/** + * Tells which export an import takes: `import MenuItem from` takes `default`, `import {X as Y} from` takes `X`. + * @param {{type: 'ImportSpecifier' | 'ImportDefaultSpecifier' | 'ImportNamespaceSpecifier', imported?: {type: string, name?: string, value?: string}}} specifier + * @returns {string | null} null for a namespace import + */ +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; +} + +/** + * 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 {{type: string} | null | undefined} node the expression a variable is initialized with + * @returns {string[]} + */ +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 []; + } +} + +/** + * 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) { + 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..f747cb0fee82 --- /dev/null +++ b/config/oxlint/oxlint.seatbelt.tsv @@ -0,0 +1,2468 @@ +# 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/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-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/index.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/components/Attachments/AttachmentCarousel/Pager/index.tsx" "react-hooks/set-state-in-effect" 1 +"../../src/components/Attachments/AttachmentCarousel/useCarouselArrows.ts" "no-restricted-syntax" 1 +"../../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/useChartLabelFormats.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 +"../../src/components/Charts/hooks/useChartLabelMeasurements.ts" "rulesdir/no-raw-typography" 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.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" "react-hooks/refs" 3 +"../../src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx" "react-hooks/refs" 2 +"../../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/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/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/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/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 +"../../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/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/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" "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 +"../../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/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" 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 +"../../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/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/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/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/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/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/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/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" "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/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/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/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/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/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-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 +"../../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/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" "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" "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/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" "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" 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 +"../../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" 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 +"../../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-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 +"../../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/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-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/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" "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/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/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/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 +"../../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" 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 +"../../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" 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 +"../../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/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/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/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/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" "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" 2 +"../../src/pages/iou/request/step/IOURequestStepConfirmation/index.tsx" "no-restricted-imports" 1 +"../../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" "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 +"../../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" "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/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/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/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-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 +"../../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" 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 +"../../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" "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 +"../../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" "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" 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 +"../../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" "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/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/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/RequestMoneyTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/actions/IOU/RequestMoneyTest.ts" "no-restricted-imports" 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" 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" 2 +"../../tests/perf-local/OptionsListChatPreview.perf-local.ts" "@typescript-eslint/no-unnecessary-type-assertion" 5 +"../../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/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/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/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 +"../../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/ImportedMembersPageTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 2 +"../../tests/ui/InitialSettingsPageTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/ui/IOURequestStepDistanceTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../tests/ui/IssueNewCardAssigneeStepTest.tsx" "@typescript-eslint/no-unnecessary-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" 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" 2 +"../../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/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/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 +"../../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/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/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 +"../../tests/unit/BankAccountUtilsTest.ts" "@typescript-eslint/no-unnecessary-type-assertion" 13 +"../../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" 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 +"../../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/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 +"../../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/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/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/useDismissOnMoneyRequestReportRemoval.test.ts" "@typescript-eslint/no-unnecessary-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/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 +"../../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/getLastRouteTest.ts" "@typescript-eslint/no-unnecessary-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 +"../../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" "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/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" 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 +"../../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/LazyGroupSelectionTest.tsx" "@typescript-eslint/no-unnecessary-type-assertion" 1 +"../../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/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/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/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..466451ec9b6c --- /dev/null +++ b/config/oxlint/plugins/core-rules.mjs @@ -0,0 +1,31 @@ +/** + * 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. + * + * 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'; + +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: { + 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..2611ab96d110 --- /dev/null +++ b/config/oxlint/plugins/expensify-rules.mjs @@ -0,0 +1,44 @@ +/** + * 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'; +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 in create(), which throws in a jsPlugin. + '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..f62e75940ebd --- /dev/null +++ b/config/oxlint/plugins/hosted-rules.mjs @@ -0,0 +1,128 @@ +/** + * 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'; + +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)), '../../..'); + +/** + * 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 { + mod = require(path.resolve(repoRoot, 'node_modules', packageName)); + } catch { + // 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 ?? {}; +} + +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; 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 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(), +}; + +/** + * 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, + create(context) { + const sourceCode = context.sourceCode ?? context.getSourceCode(); + // 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}, + }); + 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); + }, + }; +} + +/** + * 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)); +} + +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..0533a486e346 --- /dev/null +++ b/config/oxlint/plugins/rc-rules.mjs @@ -0,0 +1,51 @@ +/** + * 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'; + +/** + * 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: { + 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..a8c9badfc1a7 --- /dev/null +++ b/config/oxlint/preferLocaleCompareFromContext.mjs @@ -0,0 +1,65 @@ +/** + * 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'; + +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'); + +/** + * 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; +} + +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: [], +}; + +/** + * Reports every `x.localeCompare(...)` outside tests. + * @param {import('eslint').Rule.RuleContext} context + * @returns {import('eslint').Rule.RuleListener} + */ +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..b648000070d8 --- /dev/null +++ b/config/oxlint/reactCompilerGate.mjs @@ -0,0 +1,93 @@ +/** + * 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 {isFileMemoized} from '../reactCompiler/checkWithOxc.mjs'; + +const memoizationCache = new Map(); + +/** + * The lint pipeline's memoization verdict, remembered per file because compiling is slow. + * @param {string} filename + * @param {string} sourceText + * @returns {boolean} + */ +function isFileMemoizedOnce(filename, sourceText) { + if (!memoizationCache.has(filename)) { + memoizationCache.set(filename, isFileMemoized(sourceText, filename)); + } + 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); + } + 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); +} + +/** + * Drops the rule's reports in files React Compiler memoizes, 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, + create(context) { + const gatedContext = Object.create(context, { + report: { + // 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(); + const sourceText = (context.sourceCode ?? context.getSourceCode()).text; + if (isFileMemoizedOnce(filename, sourceText)) { + return; + } + } + return context.report(...args); + }, + }, + }); + return rule.create(gatedContext); + }, + }; +} + +/** + * 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))); +} + +export {withFullGating, withMessageGating}; diff --git a/config/oxlint/reactCompilerRust.mjs b/config/oxlint/reactCompilerRust.mjs new file mode 100644 index 000000000000..bf0cab081773 --- /dev/null +++ b/config/oxlint/reactCompilerRust.mjs @@ -0,0 +1,257 @@ +/** + * 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'; + +import oxcReactCompilerConfig from '../babel/oxcReactCompilerConfig.js'; + +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 compiler options are wrong; ignoring it would silence every rule. +const CONFIG_CATEGORY = 'Config'; + +// ESLint does not enable the rules behind these categories. +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']); + +// 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 = { + validateNoSetStateInEffects: true, + // The plugin spells this `validateNoJSXInTryStatements`; oxc spells it with a lowercase `sx`. + validateNoJsxInTryStatements: true, + validateNoImpureFunctionsInRender: true, + validateStaticComponents: true, +}; + +// 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(); + +/** + * 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'; + } + if (ext === 'ts' || ext === 'mts' || ext === 'cts') { + return 'ts'; + } + 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; + } + 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, 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]; + 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); + } + } + + /** + * @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; + while (low < high) { + const mid = Math.ceil((low + high) / 2); + if (lineBytes[mid] <= offset) { + low = mid; + } else { + high = mid - 1; + } + } + + 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]}; + }; +} + +/** + * 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 { + const buildOptions = oxcReactCompilerConfig(); + result = transformSync(filename, sourceText, { + lang: getLang(path.extname(filename).slice(1)), + reactCompiler: { + ...buildOptions, + outputMode: 'lint', + // Without it the binding returns only fatal diagnostics. + reportDiagnostics: true, + flowSuppressions: false, + environment: {...buildOptions.environment, ...ENVIRONMENT}, + }, + }); + } catch (error) { + // 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}`); + } + return []; + } + + const errors = result.errors ?? []; + + 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 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 []; + } + + 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]; + // 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), + end: offsetToPoint(label.end ?? label.start), + }, + message: buildMessage(category, error), + }); + } + 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)); + } + 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..1985a56f24cd --- /dev/null +++ b/config/oxlint/ruleNames.mjs @@ -0,0 +1,110 @@ +/** + * 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', +}; + +// 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', + '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', +}; + +const HOSTED_RULE_ESLINT_ID = { + 'no-deprecated-self-closing-jsx': '@typescript-eslint/no-deprecated', +}; + +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) { + 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') { + 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/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/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..3f15e9dd6e6f --- /dev/null +++ b/config/reactCompiler/suppressedRules.mjs @@ -0,0 +1,10 @@ +/** + * 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/; + +export {EXHAUSTIVE_DEPS_USECALLBACK_USEMEMO_PATTERN, RULES_SUPPRESSED_BY_REACT_COMPILER}; diff --git a/cspell.json b/cspell.json index a8ba6acd0e15..e2bf99871dda 100644 --- a/cspell.json +++ b/cspell.json @@ -142,6 +142,11 @@ "Erste", "Español", "detekt", + "oxfmtrc", + "oxlint", + "oxlintrc", + "tsgo", + "tsgolint", "Fabrikam", "fillyjonk", "hemulen", @@ -952,6 +957,7 @@ "proguard", "purchaseamount", "purchasecurrency", + "purgeable", "QAPR", "QBWC", "qrcode", @@ -1020,6 +1026,8 @@ "socklen", "syscalls", "serveo", + "nonconstructor", + "setstate", "setuptools", "sharee", "shareeEmail", @@ -1295,6 +1303,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 d21797f34977..936b86bb9432 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..25569d8c7394 --- /dev/null +++ b/oxlint.config.mts @@ -0,0 +1,1300 @@ +/** + * 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'; + +// 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', + 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 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 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."}, + { + 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.'}, +]; + +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.', + }, +]; + +// 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.'}, +]; + +// 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'], + // 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'}, + {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'}, + ], + // 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'], + // 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: [ + '!**/.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', + // 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', + '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/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', + // Needs typeChecker.isArrayType; `unicorn/prefer-at` stands in. + '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', + // config/oxlint/reactCompilerGate.mjs drops its reports in files React Compiler memoizes. + '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', + // 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', + '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'}], + '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', + // The native port also fires inside Jest mock factories; `hosted/function-component-definition` runs instead. + 'react/function-component-definition': 'off', + // 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', + '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.'}, + ], + // 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 + // 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', + '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', + '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', + 'core/no-octal': 'error', + 'core/no-octal-escape': 'error', + 'core/no-undef-init': 'error', + 'core/strict': ['error', 'never'], + 'one-var': ['error', 'never'], + // 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', + '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}], + // oxlint's name for ESLint's deprecated `no-new-symbol`; see OXLINT_RULE_RENAMES. + 'no-new-native-nonconstructor': 'error', + // `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', + }, + plugins: ['jsdoc'], + }, + { + files: ['**/*.{ts,tsx,cts,mts}'], + rules: { + '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', + // 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', + '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. + '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}], + '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: true, 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', + // 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'], + }, + { + files: ['**/scripts/**', '**/server/**', '**/.github/**'], + rules: { + '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: { + '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: { + '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: { + 'prefer-arrow-callback': 'off', + 'lodash/import-scope': 'off', + }, + jsPlugins: ['eslint-plugin-lodash'], + }, + { + files: ['**/*.{mjs,cjs}', '.storybook/**/*.{ts,tsx}', 'config/rsbuild/**/*.ts'], + rules: { + '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', + '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', + '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: { + '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.'}]}], + }, + }, + { + // 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': [ + '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: { + '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: 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': [ + '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: { + '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: { + // The lint pipeline keeps this diagnostic only when React Compiler memoizes 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: { + // 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 the TypeScript-only bans do not apply. + { + 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: { + 'max-lines': 'off', + }, + }, + { + files: ['scripts/**', '.github/**'], + rules: { + // 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', + '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 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', + }, + }, + { + files: ['**/*.{js,jsx,mjs,cjs}'], + rules: { + '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', + }, + }, + { + // 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 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 off for stories. + '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', + }, + }, + { + 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']}]}}, + { + 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 8899d9bc42c2..1c4873b9d23c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -275,8 +275,10 @@ "nitrogen": "0.37.1", "onchange": "^7.1.0", "openai": "^7.0.0", - "oxc-transform-react": "0.148.0", + "oxc-transform-react": "0.156.0", "oxfmt": "^0.55.0", + "oxlint": "1.90.0", + "oxlint-tsgolint": "7.0.2005", "patch-package": "^8.1.0-canary.1", "peggy": "^4.0.3", "portfinder": "^1.0.34", @@ -13207,9 +13209,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.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" ], @@ -13224,9 +13226,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.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" ], @@ -13241,9 +13243,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.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" ], @@ -13258,9 +13260,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.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" ], @@ -13275,9 +13277,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.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" ], @@ -13292,9 +13294,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.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" ], @@ -13309,9 +13311,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.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" ], @@ -13326,13 +13328,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.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" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -13343,13 +13348,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.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" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -13360,13 +13368,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.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" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -13377,13 +13388,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.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" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -13394,13 +13408,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.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" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -13411,13 +13428,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.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" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -13428,13 +13448,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.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" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -13445,13 +13468,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.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" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -13462,9 +13488,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.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" ], @@ -13479,9 +13505,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.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" ], @@ -13496,9 +13522,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.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" ], @@ -13513,9 +13539,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.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" ], @@ -13852,6 +13878,437 @@ "node": "^20.19.0 || >=22.12.0" } }, + "node_modules/@oxlint-tsgolint/darwin-arm64": { + "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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxlint-tsgolint/darwin-x64": { + "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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxlint-tsgolint/linux-arm64": { + "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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxlint-tsgolint/linux-x64": { + "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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxlint-tsgolint/win32-arm64": { + "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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oxlint-tsgolint/win32-x64": { + "version": "7.0.2005", + "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/win32-x64/-/win32-x64-7.0.2005.tgz", + "integrity": "sha512-ekU89cTGy9fpQzuvxIsFHrnoP8XoFjGIRgPEW6eqgY2NYjGy9pwpjF79o04WXRMJFKRrcaVqkZXUUBdLsqpQDw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oxlint/binding-android-arm-eabi": { + "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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-android-arm64": { + "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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-arm64": { + "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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-x64": { + "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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-freebsd-x64": { + "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" + ], + "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.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" + ], + "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.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" + ], + "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.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" + ], + "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.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" + ], + "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.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" + ], + "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.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" + ], + "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.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" + ], + "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.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" + ], + "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.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" + ], + "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.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" + ], + "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.90.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.90.0.tgz", + "integrity": "sha512-SMhk7ozwoxYDKfYqGZCQEfULOKDneF9uhaoIf2neoDLWvNtjY9yVeL7YHmE5L9WdKvMpWNwwZxhenxBu6AgfOg==", + "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.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" + ], + "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.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" + ], + "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.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" + ], + "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, @@ -35322,9 +35779,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.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": { @@ -35334,25 +35791,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.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": { @@ -35407,6 +35864,73 @@ } } }, + "node_modules/oxlint": { + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.90.0.tgz", + "integrity": "sha512-RkTN9EmgIUpLuhNoLhO3OliMGJlvb3Dk7XVjBZnfoMCHrVpuz5ESd97klfiyYeV7XfA2lNLnDCPMkUI+Qw5How==", + "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.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": { + "oxlint-tsgolint": { + "optional": true + }, + "vite-plus": { + "optional": true + } + } + }, + "node_modules/oxlint-tsgolint": { + "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.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": { "version": "3.1.0", "license": "MIT", diff --git a/package.json b/package.json index 228d4d6286c6..4f118778e488 100644 --- a/package.json +++ b/package.json @@ -351,8 +351,10 @@ "nitrogen": "0.37.1", "onchange": "^7.1.0", "openai": "^7.0.0", - "oxc-transform-react": "0.148.0", + "oxc-transform-react": "0.156.0", "oxfmt": "^0.55.0", + "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/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 e8381f7d57c9..a35a3dbcbb9d 100644 --- a/scripts/lint/index.ts +++ b/scripts/lint/index.ts @@ -9,26 +9,60 @@ * 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 checkOnyxLintBypass from '../checkOnyxLintBypass'; import checkOnyxLintDisableReason from '../checkOnyxLintDisableReason'; 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', @@ -40,6 +74,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, a positive integer or `auto` (by cores and free memory)', + default: 'auto', + parse: (value: string) => value, + }, + }, positionalArgs: [ { name: 'targets', @@ -55,15 +106,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}); + } + 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..b7858feef962 --- /dev/null +++ b/scripts/lint/oxlint/OxlintLinter.ts @@ -0,0 +1,507 @@ +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'; +// `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 = { + 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; + shards?: string; +}; + +type OxlintLeg = { + label: string; + args: string[]; + lintedFiles: readonly string[]; +}; + +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; + +// 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()` 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']); + 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 { + // 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)); +} + +// 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(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[][] { + 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; + // 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'); +} + +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; +} + +function joinDiagnosticText(diagnostic: OxlintDiagnostic): string { + return [diagnostic.message, diagnostic.help, diagnostic.note].filter(Boolean).join('\n'); +} + +/** 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); +} + +function toLintMessage(projectRoot: string, diagnostic: OxlintDiagnostic, ruleID: string | null): 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, + }; +} + +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); + 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(); + 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(diagnostic.code ? toLintMessage(projectRoot, diagnostic, oxlintCodeToESLintRuleID(diagnostic.code)) : toParseErrorMessage(projectRoot, diagnostic)); + 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); +} + +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 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); + 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); + } + + // 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) + .map((diagnostic) => ` ${diagnostic.filename}: ${diagnostic.message.split('\n').slice(0, 3).join(' ')}`) + .join('\n'); + return fatal(`Oxlint reported ${crashes.length} JS plugin crash(es):\n${sample}`, '', stderr, exitCode); + } + + return { + files: normalizeOxlintDiagnostics(parsed.diagnostics, projectRoot, lintedFiles), + exitCode, + stderr, + }; +} + +/** + * 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. + */ +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).nothrow().quiet(); + } + + private async writeLegConfigs(): Promise<{ + jsPluginsConfig: string; + typeAwareConfig: string; + 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)); + 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 = 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'); + } + + 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) { + plan = 'Oxlint plan: 1 process, stock config.'; + 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).`; + 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', + // Same files and findings as passing lintedFiles (measured), with a far shorter argv. + 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 4e5f0dd6379a..5464c3fbd96c 100644 --- a/scripts/lint/processors/ReactCompilerFilter.ts +++ b/scripts/lint/processors/ReactCompilerFilter.ts @@ -3,12 +3,14 @@ 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'; import Processor from '../Processor'; -const RULES_SUPPRESSED_BY_REACT_COMPILER = new Set(['react/jsx-no-constructed-context-values', 'rulesdir/no-inline-useOnyx-selector']); const MANUAL_MEMOIZATION_RULE = 'rulesdir/no-manual-memoization'; -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 = [ 'config/babel/oxcReactCompilerConfig.js', @@ -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 diff --git a/scripts/lint/processors/Seatbelt.ts b/scripts/lint/processors/Seatbelt.ts index 5beddb43b98a..59e6136ea6e7 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`); + // stdout carries the --format=json report. + 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 a96731877eee..1ff6e07d8e72 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'); @@ -173,7 +222,10 @@ describe('filterReactCompilerMessages', () => { it('drops suppressible messages when the compiler memoizes 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); @@ -187,7 +239,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); }); @@ -201,7 +258,12 @@ 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); }); @@ -217,13 +279,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'); }); @@ -233,6 +307,388 @@ 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-hooks(rules-of-hooks)', 'react-hooks/rules-of-hooks'], + ]; + 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 crashing JS plugin as fatal, not a finding', () => { + // 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 + 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('JS plugin crash'); + 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}}]}); + 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); + }); + + 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('')).toThrow('--shards'); + expect(resolveShardCount('6')).toBe(6); + 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', () => { + 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, 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); + }); + + 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, 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'); + }); + + it('only a shard that produced no JSON is worth retrying', () => { + 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, labels: [], message: 'Error running JS plugin.\nError: plugin blew up'})]))).toBe(false); + expect(producedNoJSON(oxlintStdout([oxlintDiagnostic()], 1))).toBe(false); + expect(producedNoJSON(oxlintStdout([]))).toBe(false); + + 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 () => { + // 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); + + 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"] }