Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
e999f03
Split app TypeScript into web and native programs
roryabraham Aug 26, 2026
3242849
Fix knip, spellcheck, and React Compiler CI
roryabraham Aug 26, 2026
d7cdfb4
Widen native PopoverProvider action stubs
roryabraham Aug 26, 2026
7c7ba8f
Merge remote-tracking branch 'origin/main' into rory/tsconfig-cleanup…
roryabraham Sep 1, 2026
29a7c31
Fix lint project mappings and casts
roryabraham Sep 1, 2026
62eca1f
Include linted mocks in TypeScript project
roryabraham Sep 1, 2026
c8b3dd7
Merge remote-tracking branch 'origin/main' into rory/tsconfig-cleanup…
roryabraham Sep 1, 2026
748136a
Exclude mocks from app global restrictions
roryabraham Sep 1, 2026
d5b7855
Fix lint mocks and hybrid app tests
roryabraham Sep 2, 2026
624980b
Merge remote-tracking branch 'origin/main' into rory/tsconfig-cleanup…
roryabraham Sep 2, 2026
0b562dc
Merge remote-tracking branch 'origin/main' into rory/tsconfig-cleanup…
roryabraham Sep 2, 2026
4d91cd3
Merge origin/main into tsconfig cleanup
roryabraham Sep 2, 2026
11a310c
Fix hybrid app actions test mock
roryabraham Sep 2, 2026
f4bf402
Update generated GitHub Actions bundles
roryabraham Sep 2, 2026
8bb854c
Merge remote-tracking branch 'origin/main' into rory/tsconfig-cleanup…
roryabraham Sep 3, 2026
feff88d
Merge remote-tracking branch 'origin/main' into rory/tsconfig-cleanup…
roryabraham Sep 3, 2026
0c5f106
chore: keep required lint project mappings
roryabraham Sep 3, 2026
22bf2b7
chore: lint Nitro sources with module config
roryabraham Sep 3, 2026
332cd0b
fix: narrow font size style type
roryabraham Sep 3, 2026
9b2842b
test: restore Hybrid App mock setup
roryabraham Sep 3, 2026
d5c1434
refactor: share platform no-op contracts
roryabraham Sep 3, 2026
dc09e55
fix: align lint and Jest project roots
roryabraham Sep 3, 2026
9bfd921
fix: keep monthly submit date on current day
roryabraham Sep 3, 2026
65847e8
Merge remote-tracking branch 'origin/main' into rory/tsconfig-cleanup…
roryabraham Sep 9, 2026
dbf0b28
fix: resolve type-safety lint errors surfaced by the web/native split
roryabraham Sep 9, 2026
d554f2b
chore(eslint): merge duplicate native tsconfig mappings
roryabraham Sep 9, 2026
0a3c788
docs(patches): link upstream PRs for web type fixes
roryabraham Sep 9, 2026
9ccf1d0
chore: bump live-markdown to 0.1.339, drop web export patch
roryabraham Sep 9, 2026
03ef5e1
refactor: rename bare web-only impls to .web, drop native excludes
roryabraham Sep 10, 2026
5f0d60e
Revert "refactor: rename bare web-only impls to .web, drop native exc…
roryabraham Sep 10, 2026
c13565b
Re-apply .web rename, teach knip to resolve platform-suffixed modules
roryabraham Sep 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 43 additions & 4 deletions config/eslint/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -280,9 +280,9 @@ const config = defineConfig([

languageOptions: {
parserOptions: {
// The app project, not the root solution: the solution owns no files, so typed linting
// has nothing to resolve against there.
project: path.resolve(projectRoot, 'tsconfig.app.json'),
// The web app project, not the root solution: the solution owns no files, so typed linting
// has nothing to resolve against there. Native files override this below.
project: path.resolve(projectRoot, 'tsconfig.app.web.json'),
projectService: false,
},

Expand Down Expand Up @@ -707,6 +707,12 @@ const config = defineConfig([

{
files: ['modules/ExpensifyNitroUtils/src/**/*'],
languageOptions: {
parserOptions: {
project: path.resolve(projectRoot, 'modules/ExpensifyNitroUtils/tsconfig.json'),
projectService: false,
},
},
rules: {
'@typescript-eslint/consistent-type-definitions': 'off',
},
Expand All @@ -723,6 +729,39 @@ const config = defineConfig([
},
},

{
files: [
'src/**/*.native.ts',
'src/**/*.native.tsx',
'src/**/*.ios.ts',
'src/**/*.ios.tsx',
'src/**/*.android.ts',
'src/**/*.android.tsx',
'src/types/native/**/*.d.ts',
'modules/background-task/src/**/*',
'modules/hybrid-app/src/**/*',
],
// `tsconfig.app.native.json` excludes `__mocks__` (those belong to the Jest program), so mock
// files matching a platform suffix above must keep resolving through the Jest mapping instead.
ignores: ['src/**/__mocks__/**'],
languageOptions: {
parserOptions: {
project: path.resolve(projectRoot, 'tsconfig.app.native.json'),
projectService: false,
},
},
},

{
files: ['types/env.d.ts'],
languageOptions: {
parserOptions: {
project: path.resolve(projectRoot, 'tsconfig.app.web.json'),
projectService: false,
},
},
},

{
// `prompts` is not Bun code, but the Bun program is the one that owns it: `scripts`,
// `evals` and `tests/tooling` are its callers. (The Node program lists it too, for the
Expand All @@ -737,7 +776,7 @@ const config = defineConfig([
},

{
files: ['.github/**/*.{ts,tsx,js}', 'web/proxy.ts', 'config/**/*.{ts,tsx,mts,mjs,cjs,js}'],
files: ['.github/**/*.{ts,tsx,js}', 'web/proxy.ts', 'config/**/*.{ts,tsx,mts,mjs,cjs,js}', 'knip.config.ts'],
languageOptions: {
parserOptions: {
project: path.resolve(projectRoot, 'tsconfig.node.json'),
Expand Down
12 changes: 6 additions & 6 deletions config/eslint/eslint.seatbelt.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@
"../../src/components/CheckboxWithLabel.tsx" "no-restricted-syntax" 1
"../../src/components/ColumnsSettingsList.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/components/Composer/implementation/index.native.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/components/Composer/implementation/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 4
"../../src/components/Composer/implementation/index.tsx" "react-hooks/set-state-in-effect" 2
"../../src/components/Composer/implementation/index.web.tsx" "@typescript-eslint/no-unsafe-type-assertion" 4
"../../src/components/Composer/implementation/index.web.tsx" "react-hooks/set-state-in-effect" 2
"../../src/components/ConfirmationPage.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2
"../../src/components/ConfirmModal.tsx" "@typescript-eslint/no-deprecated/ConfirmModal" 1
"../../src/components/ConnectToNetSuiteFlow/index.tsx" "react-hooks/refs" 5
Expand Down Expand Up @@ -741,7 +741,7 @@
"../../src/libs/Navigation/AppNavigator/createSplitNavigator/SplitRouter.ts" "@typescript-eslint/no-unsafe-type-assertion" 4
"../../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/FreezeWrapper/index.tsx" "react-hooks/set-state-in-effect" 1
"../../src/libs/Navigation/AppNavigator/FreezeWrapper/index.web.tsx" "react-hooks/set-state-in-effect" 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
Expand Down Expand Up @@ -825,7 +825,7 @@
"../../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/Pusher/index.web.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/ReportActionFollowupUtils/tokenizeForReveal.ts" "@typescript-eslint/no-unsafe-type-assertion" 3
Expand Down Expand Up @@ -974,7 +974,7 @@
"../../src/pages/iou/request/step/IOURequestStepDistanceGPS/BackgroundLocationPermissionsFlow/index.ios.tsx" "@typescript-eslint/no-deprecated/ConfirmModal" 2
"../../src/pages/iou/request/step/IOURequestStepDistanceGPS/GPSButtons/index.tsx" "@typescript-eslint/no-deprecated/ConfirmModal" 3
"../../src/pages/iou/request/step/IOURequestStepDistanceMap.tsx" "react-hooks/set-state-in-effect" 1
"../../src/pages/iou/request/step/IOURequestStepOdometerImage/index.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/pages/iou/request/step/IOURequestStepOdometerImage/index.web.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
Expand Down Expand Up @@ -1271,7 +1271,7 @@
"../../src/selectors/AdvancedSearchFiltersForm.ts" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/selectors/Domain.ts" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/selectors/Policy.ts" "@typescript-eslint/no-unsafe-type-assertion" 1
"../../src/setup/addUtilsToWindow.ts" "@typescript-eslint/no-unsafe-type-assertion" 20
"../../src/setup/addUtilsToWindow.web.ts" "@typescript-eslint/no-unsafe-type-assertion" 19
"../../src/setup/moduleInitPolyfill.ts" "@typescript-eslint/no-unsafe-type-assertion" 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
Expand Down
3 changes: 1 addition & 2 deletions evals/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ Two things about the wiring are load-bearing and easy to break:
explicit paths too, so running the evals on purpose means overriding it with a pattern that matches
nothing.

`evals/` has its own `tsconfig.json` rather than joining the root project, because `@types/bun`'s
globals conflict with the app's. It isn't in the set of projects `npm run typecheck` gates on, so typecheck it with `npm run typecheck -- evals`.
`evals/` is type-checked as part of the Bun project (`tsconfig.bun.json`) and is included in `npm run typecheck`.

## ProposalPolice

Expand Down
4 changes: 2 additions & 2 deletions jest/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import setupMockImages from './setupMockImages';

// Needed for tests to have the necessary environment variables set
if (!('GITHUB_REPOSITORY' in process.env)) {
(process.env as NodeJS.ProcessEnv).GITHUB_REPOSITORY_OWNER = 'Expensify';
(process.env as NodeJS.ProcessEnv).GITHUB_REPOSITORY = 'Expensify/App';
process.env.GITHUB_REPOSITORY_OWNER = 'Expensify';
process.env.GITHUB_REPOSITORY = 'Expensify/App';
}

setupMockImages();
Expand Down
115 changes: 115 additions & 0 deletions knip.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/* eslint-disable @typescript-eslint/naming-convention -- object keys here are file extensions and workspace paths, not identifiers */
import type {KnipConfig, KnipConfiguration} from 'knip';

// Identity compilers for React Native's platform-suffixed files. These are already plain
// TypeScript, so no transform is needed — registering them here only teaches knip's module
// resolver to try `Foo.web.ts`, `Foo.native.tsx`, etc. as fallbacks when a bare `Foo.ts`/`index.ts`
// doesn't exist, the same way TypeScript's `moduleSuffixes` and Metro/rsbuild's platform
// resolution already do. See CONTRIBUTING note in tsconfig.app.native.json / tsconfig.app.web.json.
const identity = (source: string) => source;
const platformCompilers: KnipConfiguration['compilers'] = {
'.web.ts': identity,
'.web.tsx': identity,
'.native.ts': identity,
'.native.tsx': identity,
'.ios.ts': identity,
'.ios.tsx': identity,
'.android.ts': identity,
'.android.tsx': identity,
};

const config: KnipConfig = {
workspaces: {
'.': {
entry: [
'index.js',
'wdyr.ts',
'src/App.tsx',
'src/HybridAppHandler.tsx',
'scripts/**/*.{js,mjs,ts}',
'web/proxy.ts',
'config/rsbuild/**/*.{js,mjs,cjs,ts}',
'.github/scripts/**/*.ts',
'tests/tooling/**/*.ts',
'.github/actions/javascript/**/*.ts',
'tests/globals.d.ts',
'.storybook/**/*.{js,ts,tsx}',
'metro.config.js',
'config/babel/oxcJestTransformer.js',
'eslint.changed.config.mjs',
'react-native.config.js',
'rock.config.mjs',
'src/components/HeaderWithBackButtonComposed/index.tsx',
],
project: [
'src/**/*.{js,jsx,ts,tsx}',
'tests/**/*.{js,jsx,ts,tsx}',
'__mocks__/**/*.{js,jsx,ts,tsx}',
'web/**/*.{js,jsx,ts,tsx}',
'config/**/*.{js,mjs,ts,tsx}',
'scripts/**/*.{js,ts}',
'jest/**/*.{js,ts}',
'.storybook/**/*.{js,ts,tsx}',
'.github/actions/javascript/**/*.ts',
],
ignore: ['.github/actions/**/index.js', 'tests/perf-test/**', 'web/snippets/gib.js', 'src/libs/actions/connections/FinancialForce.ts', 'src/setup/telemetry/noopExpoUpdates.ts'],
eslint: {
config: ['config/eslint/eslint.config.mjs', 'eslint.changed.config.mjs'],
},
rsbuild: {
config: ['config/rsbuild/rsbuild.config.ts', '.storybook/rsbuild.config.ts'],
},
babel: {
config: ['babel.config.js'],
},
jest: {
config: ['jest.config.js'],
},
storybook: {
config: ['.storybook/main.ts'],
},
},
'server/victory-chart-renderer': {
entry: ['src/bootstrap.tsx', 'tests/**/*.test.ts'],
project: ['src/**/*.{ts,tsx}', 'scripts/**/*.ts', 'tests/**/*.{ts,tsx}'],
},
},
ignoreDependencies: [
'@expensify/react-native-hybrid-app',
'focus-trap',
'group-ib-fp',
'react-native-image-size',
'react-native-picker-select',
'react-native-quick-crypto',
'react-native-quick-base64',
'lodash',
'@babel/plugin-proposal-private-methods',
'@babel/plugin-proposal-private-property-in-object',
'@babel/plugin-transform-export-namespace-from',
'babel-plugin-module-resolver',
'babel-plugin-react-compiler',
'babel-plugin-transform-remove-console',
'html-entities',
'@fullstory/babel-plugin-react-native',
'@fullstory/babel-plugin-annotate-react',
'eslint-config-airbnb-typescript',
'eslint-config-prettier',
'eslint-plugin-storybook',
'@dword-design/eslint-plugin-import-alias',
'bun',
'shellcheck',
'patch-package',
'diff-so-fancy',
'@babel/plugin-proposal-class-properties',
// Pre-existing phantom deps (used directly but only present transitively); unrelated to
// this config's compilers. Listed here rather than `dependencies` to avoid an unrelated
// package.json/lockfile change.
'pdfjs-dist',
'react-pdf',
'react-freeze',
],
ignoreBinaries: ['metro-symbolicate', 'mkcert', 'openssl'],
compilers: platformCompilers,
};

export default config;
92 changes: 0 additions & 92 deletions knip.json

This file was deleted.

12 changes: 7 additions & 5 deletions modules/ExpensifyNitroUtils/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
{
// The app project, not the root solution: the solution owns no compiler options.
"extends": "../../tsconfig.app.json",
// Shared compiler options only. Extending the native app project inherits its
// `exclude` of `modules/ExpensifyNitroUtils/**`, which leaves this program empty.
"extends": "../../tsconfig.base.json",
"compilerOptions": {
// Without this the app project's build info path is inherited and this module would write
// over it.
"tsBuildInfoFile": "./tsconfig.ts7.tsbuildinfo",
"outDir": "./lib"
"outDir": "./lib",
"types": [],
"lib": ["ESNext"],
"moduleSuffixes": [".ios", ".android", ".native", ""]
},
"include": ["src/**/*"]
}
Loading
Loading