From 15c26eb6d621a85df9eecb2b8a5fa009fa2fe040 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Fri, 14 Apr 2023 20:49:41 +0200 Subject: [PATCH] [ESLint] enable `promise/prefer-await-to-then` for non React packages (#3120) Co-authored-by: Ted Thibodeau Jr --- .changeset/tasty-glasses-report.md | 9 ++++ .eslintrc.js | 8 +++ .../src/async-helpers/index.ts | 54 ++++++++----------- .../graphql-language-service-cli/src/cli.ts | 9 ++-- .../src/GraphQLCache.ts | 53 +++++++++--------- .../src/__tests__/MessageProcessor-test.ts | 8 +-- .../src/startServer.ts | 7 ++- packages/monaco-graphql/src/initializeMode.ts | 2 + .../src/providers/exec-content.ts | 9 ++-- packages/vscode-graphql/src/server/index.ts | 12 +++-- 10 files changed, 91 insertions(+), 80 deletions(-) create mode 100644 .changeset/tasty-glasses-report.md diff --git a/.changeset/tasty-glasses-report.md b/.changeset/tasty-glasses-report.md new file mode 100644 index 00000000000..2ba568d2296 --- /dev/null +++ b/.changeset/tasty-glasses-report.md @@ -0,0 +1,9 @@ +--- +'@graphiql/toolkit': patch +'graphql-language-service-server': patch +'monaco-graphql': patch +'vscode-graphql': patch +'vscode-graphql-execution': patch +--- + +prefer await to then diff --git a/.eslintrc.js b/.eslintrc.js index 79407c08654..c0a781d7f4c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -366,5 +366,13 @@ module.exports = { 'import/no-unresolved': ['error', { ignore: ['^node:', 'vscode'] }], }, }, + { + files: ['packages/**'], + // ignore React packages because it's ugly to have `async IIFE` inside `useEffect` + excludedFiles: ['packages/graphiql/**', 'packages/graphiql-react/**'], + rules: { + 'promise/prefer-await-to-then': 'error', + }, + }, ], }; diff --git a/packages/graphiql-toolkit/src/async-helpers/index.ts b/packages/graphiql-toolkit/src/async-helpers/index.ts index 754cdb38259..700e11c29c6 100644 --- a/packages/graphiql-toolkit/src/async-helpers/index.ts +++ b/packages/graphiql-toolkit/src/async-helpers/index.ts @@ -52,42 +52,34 @@ export function isAsyncIterable( ); } -function asyncIterableToPromise( +async function asyncIterableToPromise( input: AsyncIterable | AsyncIterableIterator, ): Promise { - return new Promise((resolve, reject) => { - // Also support AsyncGenerator on Safari iOS. - // As mentioned in the isAsyncIterable function there is no Symbol.asyncIterator available - // so every AsyncIterable must be implemented using AsyncGenerator. - const iteratorReturn = ( - 'return' in input ? input : input[Symbol.asyncIterator]() - ).return?.bind(input); - const iteratorNext = ( - 'next' in input ? input : input[Symbol.asyncIterator]() - ).next.bind(input); + // Also support AsyncGenerator on Safari iOS. + // As mentioned in the isAsyncIterable function, there is no Symbol.asyncIterator available, + // so every AsyncIterable must be implemented using AsyncGenerator. + const iteratorReturn = ( + 'return' in input ? input : input[Symbol.asyncIterator]() + ).return?.bind(input); + const iteratorNext = ( + 'next' in input ? input : input[Symbol.asyncIterator]() + ).next.bind(input); - iteratorNext() - .then(result => { - resolve(result.value); - // ensure cleanup - void iteratorReturn?.(); - }) - .catch(err => { - reject(err); - }); - }); + const result = await iteratorNext(); + // ensure cleanup + void iteratorReturn?.(); + return result.value; } -export function fetcherReturnToPromise( +export async function fetcherReturnToPromise( fetcherResult: FetcherReturnType, ): Promise { - return Promise.resolve(fetcherResult).then(result => { - if (isAsyncIterable(result)) { - return asyncIterableToPromise(result); - } - if (isObservable(result)) { - return observableToPromise(result); - } - return result; - }); + const result = await fetcherResult; + if (isAsyncIterable(result)) { + return asyncIterableToPromise(result); + } + if (isObservable(result)) { + return observableToPromise(result); + } + return result; } diff --git a/packages/graphql-language-service-cli/src/cli.ts b/packages/graphql-language-service-cli/src/cli.ts index 5dec9f30282..75647184aa7 100644 --- a/packages/graphql-language-service-cli/src/cli.ts +++ b/packages/graphql-language-service-cli/src/cli.ts @@ -117,21 +117,22 @@ if (command === 'server') { }); const options: { [key: string]: any } = {}; - if (argv?.port) { + if (argv.port) { options.port = argv.port; } - if (argv?.method) { + if (argv.method) { options.method = argv.method; } - if (argv?.configDir) { + if (argv.configDir) { options.configDir = argv.configDir; } + // eslint-disable-next-line promise/prefer-await-to-then -- don't know if I can use top level await here startServer(options).catch(error => { const logger = new Logger(); logger.error(String(error)); }); } else { - client(command as string, argv as { [key: string]: string }); + client(command as string, argv as Record); } // Exit the process when stream closes from remote end. diff --git a/packages/graphql-language-service-server/src/GraphQLCache.ts b/packages/graphql-language-service-server/src/GraphQLCache.ts index a557031cfd1..146b160783f 100644 --- a/packages/graphql-language-service-server/src/GraphQLCache.ts +++ b/packages/graphql-language-service-server/src/GraphQLCache.ts @@ -679,33 +679,32 @@ export class GraphQLCache implements GraphQLCacheInterface { const responses: GraphQLFileInfo[] = []; while (queue.length) { const chunk = queue.splice(0, MAX_READS); - const promises = chunk.map(fileInfo => - this.promiseToReadGraphQLFile(fileInfo.filePath) - .catch(error => { - // eslint-disable-next-line no-console - console.log('pro', error); - /** - * fs emits `EMFILE | ENFILE` error when there are too many - * open files - this can cause some fragment files not to be - * processed. Solve this case by implementing a queue to save - * files failed to be processed because of `EMFILE` error, - * and await on Promises created with the next batch from the - * queue. - */ - if (error.code === 'EMFILE' || error.code === 'ENFILE') { - queue.push(fileInfo); - } - }) - .then((response: GraphQLFileInfo | void) => { - if (response) { - responses.push({ - ...response, - mtime: fileInfo.mtime, - size: fileInfo.size, - }); - } - }), - ); + const promises = chunk.map(async fileInfo => { + try { + const response = await this.promiseToReadGraphQLFile( + fileInfo.filePath, + ); + responses.push({ + ...response, + mtime: fileInfo.mtime, + size: fileInfo.size, + }); + } catch (error: any) { + // eslint-disable-next-line no-console + console.log('pro', error); + /** + * fs emits `EMFILE | ENFILE` error when there are too many + * open files - this can cause some fragment files not to be + * processed. Solve this case by implementing a queue to save + * files failed to be processed because of `EMFILE` error, + * and await on Promises created with the next batch from the + * queue. + */ + if (error.code === 'EMFILE' || error.code === 'ENFILE') { + queue.push(fileInfo); + } + } + }); await Promise.all(promises); // eslint-disable-line no-await-in-loop } diff --git a/packages/graphql-language-service-server/src/__tests__/MessageProcessor-test.ts b/packages/graphql-language-service-server/src/__tests__/MessageProcessor-test.ts index 3e95f7c7946..dd87770581d 100644 --- a/packages/graphql-language-service-server/src/__tests__/MessageProcessor-test.ts +++ b/packages/graphql-language-service-server/src/__tests__/MessageProcessor-test.ts @@ -270,10 +270,10 @@ describe('MessageProcessor', () => { const params = { textDocument: initialDocument.textDocument, position }; // Should throw because file has been deleted from cache - return messageProcessor - .handleCompletionRequest(params) - .then(result => expect(result).toEqual(null)) - .catch(() => {}); + try { + const result = await messageProcessor.handleCompletionRequest(params); + expect(result).toEqual(null); + } catch {} }); // modified to work with jest.mock() of WatchmanClient diff --git a/packages/graphql-language-service-server/src/startServer.ts b/packages/graphql-language-service-server/src/startServer.ts index 117355b8c8e..c0cfd006909 100644 --- a/packages/graphql-language-service-server/src/startServer.ts +++ b/packages/graphql-language-service-server/src/startServer.ts @@ -170,7 +170,7 @@ export default async function startServer( const { port, hostname } = options; const socket = net - .createServer(client => { + .createServer(async client => { client.setEncoding('utf8'); reader = new SocketMessageReader(client); writer = new SocketMessageWriter(client); @@ -178,14 +178,13 @@ export default async function startServer( socket.close(); process.exit(0); }); - return initializeHandlers({ + const s = await initializeHandlers({ reader, writer, logger, options: finalOptions, - }).then(s => { - s.listen(); }); + s.listen(); }) .listen(port, hostname); return; diff --git a/packages/monaco-graphql/src/initializeMode.ts b/packages/monaco-graphql/src/initializeMode.ts index 57a05aee180..e8424b2bbf0 100644 --- a/packages/monaco-graphql/src/initializeMode.ts +++ b/packages/monaco-graphql/src/initializeMode.ts @@ -28,6 +28,8 @@ export function initializeMode( api = createMonacoGraphQLAPI(LANGUAGE_ID, config); (languages).graphql = { api }; // export to the global monaco API + + // eslint-disable-next-line promise/prefer-await-to-then -- ignore to leave initializeMode sync void getMode().then(mode => mode.setupMode(api)); } diff --git a/packages/vscode-graphql-execution/src/providers/exec-content.ts b/packages/vscode-graphql-execution/src/providers/exec-content.ts index cdece598f11..d285d9ba5f6 100644 --- a/packages/vscode-graphql-execution/src/providers/exec-content.ts +++ b/packages/vscode-graphql-execution/src/providers/exec-content.ts @@ -108,11 +108,10 @@ export class GraphQLContentProvider implements TextDocumentContentProvider { enableScripts: true, }; - this.loadProvider() - .then() - .catch(err => { - this.html = err.toString(); - }); + // eslint-disable-next-line promise/prefer-await-to-then -- can't use async in constructor + this.loadProvider().catch(err => { + this.html = err.toString(); + }); } validUrlFromSchema(pathOrUrl: string) { diff --git a/packages/vscode-graphql/src/server/index.ts b/packages/vscode-graphql/src/server/index.ts index b2c22aed88c..f43431fee76 100644 --- a/packages/vscode-graphql/src/server/index.ts +++ b/packages/vscode-graphql/src/server/index.ts @@ -6,11 +6,13 @@ import { startServer } from 'graphql-language-service-server'; // The npm scripts are configured to only build this once before // watching the extension, so please restart the extension debugger for changes! -const start = () => { - startServer({ method: 'node' }).catch(err => { +async function start() { + try { + await startServer({ method: 'node' }); + } catch (err) { // eslint-disable-next-line no-console console.error(err); - }); -}; + } +} -start(); +void start();