From da999ad526c45475da8c5022b042c80bb64baecd Mon Sep 17 00:00:00 2001 From: Christopher Pruijsen Date: Thu, 10 Sep 2026 23:08:46 +0100 Subject: [PATCH] fix: stop static Picker.Item from referencing class type parameters TypeScript forbids static members from using the class type parameter, which broke consumer tsc since 1.16.5. --- js/__tests__/Picker.types.test.js | 69 +++++++++++++++++++++++++++++++ package.json | 2 +- typings/Picker.d.ts | 4 +- 3 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 js/__tests__/Picker.types.test.js diff --git a/js/__tests__/Picker.types.test.js b/js/__tests__/Picker.types.test.js new file mode 100644 index 0000000000..9081f790e2 --- /dev/null +++ b/js/__tests__/Picker.types.test.js @@ -0,0 +1,69 @@ +'use strict'; + +const path = require('path'); +const ts = require('typescript'); + +const TYPINGS_ROOT = path.resolve(__dirname, '../../typings'); + +function getTypingsDiagnostics() { + const compilerOptions = { + target: ts.ScriptTarget.ES6, + module: ts.ModuleKind.CommonJS, + jsx: ts.JsxEmit.React, + moduleResolution: ts.ModuleResolutionKind.NodeJs, + strict: true, + skipLibCheck: false, + noEmit: true, + esModuleInterop: true, + }; + const program = ts.createProgram( + [ + path.join(TYPINGS_ROOT, 'index.d.ts'), + path.join(TYPINGS_ROOT, 'Picker.d.ts'), + path.join(TYPINGS_ROOT, 'PickerIOS.d.ts'), + ], + compilerOptions, + ); + + return ts.getPreEmitDiagnostics(program).filter((diagnostic) => { + if (!diagnostic.file) { + return false; + } + return diagnostic.file.fileName.replace(/\\/g, '/').includes('/typings/'); + }); +} + +function formatDiagnostics(diagnostics) { + return diagnostics + .map((diagnostic) => { + const {line, character} = diagnostic.file.getLineAndCharacterOfPosition( + diagnostic.start, + ); + const message = ts.flattenDiagnosticMessageText( + diagnostic.messageText, + '\n', + ); + const fileName = path.relative(process.cwd(), diagnostic.file.fileName); + return `${fileName}:${line + 1}:${character + 1} TS${ + diagnostic.code + }: ${message}`; + }) + .join('\n'); +} + +function checkPickerTypings() { + const formatted = formatDiagnostics(getTypingsDiagnostics()); + if (formatted !== '') { + throw new Error(`Picker typings failed:\n${formatted}`); + } +} + +if (typeof describe === 'function') { + describe('Picker typings', () => { + it('does not reference class type parameters from static members', () => { + checkPickerTypings(); + }); + }); +} else { + checkPickerTypings(); +} diff --git a/package.json b/package.json index 5c2e2f5f2a..2fef86da1e 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "test": "yarn validate:eslint && yarn validate:flow && yarn validate:typescript && yarn test:jest", "validate:eslint": "eslint 'js/**/*.js' 'example/**/*.js'", "validate:flow": "flow check", - "validate:typescript": "tsc --project ./", + "validate:typescript": "tsc --project ./ && node js/__tests__/Picker.types.test.js", "test:jest": "jest js/", "test:detox:android:test:debug": "detox test -c android.emu.debug", "test:detox:android:test:release": "detox test -c android.emu.release", diff --git a/typings/Picker.d.ts b/typings/Picker.d.ts index 0e2175634a..74d57e2fce 100644 --- a/typings/Picker.d.ts +++ b/typings/Picker.d.ts @@ -122,6 +122,8 @@ export interface PickerProps extends ViewProps { onBlur?: (e: NativeSyntheticEvent) => void; } +declare class PickerItem extends React.Component, {}> {} + declare class Picker extends React.Component, {}> { /** * On Android, display the options in a dialog (this is the default). @@ -132,7 +134,7 @@ declare class Picker extends React.Component, {}> { */ static readonly MODE_DROPDOWN: 'dropdown'; - static Item: React.ComponentType>; + static Item: typeof PickerItem; /** * @platform android