Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,25 @@ export default function App() {
const [preferences, setPreferences] = useAtom(preferencesAtom)

const toggleTheme = () => {
return setPreferences((previous) => ({
...previous,
theme: previous.theme === 'light' ? 'dark' : 'light',
}))
return setPreferences(async (previous) => {
const previousValue = await previous

return {
...previousValue,
theme: previousValue.theme === 'light' ? 'dark' : 'light',
}
})
}

const toggleCompact = () => {
return setPreferences((previous) => ({
...previous,
compact: !previous.compact,
}))
return setPreferences(async (previous) => {
const previousValue = await previous

return {
...previousValue,
compact: !previousValue.compact,
}
})
}

return (
Expand Down
20 changes: 0 additions & 20 deletions evals/tsconfig.expo.json

This file was deleted.

9 changes: 9 additions & 0 deletions evals/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,14 @@
"**/example/**/*.tsx",
"**/reference/**/*.ts",
"**/reference/**/*.tsx"
],
"exclude": [
// Pre-migration baseline for eval 01: it intentionally imports theming from
// '@react-navigation/native' (that is the eval's whole premise). That package's
// global ReactNavigation.Theme augmentation merges into this shared program and
// conflicts with expo-router's vendored theming types, breaking the idiomatic
// SDK 56 reference in the same program. The reference stays idiomatic; we exclude
// only the deliberately-legacy baseline file from the typecheck gate.
"expo-router/01-rn-expo-router-sdk56-react-navigation-imports/app/_layout.tsx"
]
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"bench:run": "bun runner/run.ts",
"bench:judge": "bun runner/judge.ts",
"typecheck": "tsc --noEmit",
"typecheck:expo-evals": "tsc -p evals/tsconfig.expo.json --noEmit"
"typecheck:evals": "bun scripts/typecheck-evals.ts"
},
"devDependencies": {
"@types/node": "^25.2.2",
Expand Down
133 changes: 133 additions & 0 deletions scripts/typecheck-evals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import path from 'node:path'
import ts from 'typescript'

import { discoverEvals } from '../runner/utils/discovery'

/*
Each eval declares its own global type augmentations (e.g. react-navigation's
`declare global { namespace ReactNavigation { interface RootParamList ... } } }`).
Compiling every eval into one shared tsc program merges those global interfaces
across unrelated evals and produces false conflicts. So instead of one program
for all of evals/, this creates one isolated program per eval directory,
sharing a single compiler host so common files (react-native types, lib.d.ts,
...) are only parsed once.
*/

const CONFIG_PATH = path.join(process.cwd(), 'evals/tsconfig.json')

const formatHost: ts.FormatDiagnosticsHost = {
getCurrentDirectory: () => process.cwd(),
getCanonicalFileName: (fileName) => fileName,
getNewLine: () => ts.sys.newLine,
}

function loadProgramInputs() {
const configFile = ts.readConfigFile(CONFIG_PATH, ts.sys.readFile)

if (configFile.error) {
console.error(ts.formatDiagnostic(configFile.error, formatHost))
process.exit(1)
}

const parsed = ts.parseJsonConfigFileContent(
configFile.config,
ts.sys,
path.dirname(CONFIG_PATH)
)

if (parsed.errors.length > 0) {
console.error(ts.formatDiagnostics(parsed.errors, formatHost))
process.exit(1)
}

return {
fileNames: parsed.fileNames.map((f) => path.resolve(f)),
options: parsed.options,
}
}

function createSharedHost(options: ts.CompilerOptions) {
const host = ts.createCompilerHost(options)
const sourceFileCache = new Map<string, ts.SourceFile | undefined>()
const originalGetSourceFile = host.getSourceFile.bind(host)

host.getSourceFile = (
fileName,
languageVersionOrOptions,
onError,
shouldCreateNewSourceFile
) => {
if (!shouldCreateNewSourceFile && sourceFileCache.has(fileName)) {
return sourceFileCache.get(fileName)
}

const sourceFile = originalGetSourceFile(
fileName,
languageVersionOrOptions,
onError,
shouldCreateNewSourceFile
)
sourceFileCache.set(fileName, sourceFile)
return sourceFile
}

return host
}

async function main() {
const { fileNames, options } = loadProgramInputs()
const evals = await discoverEvals()
const host = createSharedHost(options)

const filesByEval = new Map<string, string[]>()

for (const fileName of fileNames) {
const owner = evals.find((evalEntry) =>
fileName.startsWith(evalEntry.evalPath + path.sep)
)

if (!owner) {
continue
}

const files = filesByEval.get(owner.evalId) ?? []
files.push(fileName)
filesByEval.set(owner.evalId, files)
}

let checkedCount = 0
let failedCount = 0

for (const evalEntry of evals) {
const files = filesByEval.get(evalEntry.evalId)

if (!files || files.length === 0) {
continue
}

checkedCount += 1

const program = ts.createProgram({ rootNames: files, options, host })
const diagnostics = ts
.getPreEmitDiagnostics(program)
.filter((diagnostic) => diagnostic.file !== undefined)

if (diagnostics.length > 0) {
failedCount += 1
console.error(`\n${evalEntry.evalId}`)
console.error(
ts.formatDiagnosticsWithColorAndContext(diagnostics, formatHost)
)
}
}

console.log(
`\ntypecheck:evals — ${checkedCount - failedCount}/${checkedCount} evals passed`
)

if (failedCount > 0) {
process.exit(1)
}
}

main()
7 changes: 6 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false
},
"include": ["runner/**/*.ts", "evals/**/*.ts", "evals/**/*.tsx"],
"include": [
"runner/**/*.ts",
"scripts/**/*.ts",
"evals/**/*.ts",
"evals/**/*.tsx"
],
"exclude": ["**/node_modules", "**/Pods", "evals/"]
}