From d2feff92aba979fb52fd0e5846776be223fbf11e Mon Sep 17 00:00:00 2001 From: Steve Wagner Date: Sat, 28 Nov 2020 19:37:43 +0100 Subject: [PATCH] fix: languageserver filepath on Windows (#1715) Co-authored-by: Rikki Schulte --- .../src/GraphQLCache.ts | 28 ++++------- .../src/Logger.ts | 8 +++- .../src/MessageProcessor.ts | 46 ++++++++----------- .../src/__tests__/MessageProcessor-test.ts | 15 +++--- 4 files changed, 43 insertions(+), 54 deletions(-) diff --git a/packages/graphql-language-service-server/src/GraphQLCache.ts b/packages/graphql-language-service-server/src/GraphQLCache.ts index 929a6e4dfd3..d731e464ff8 100644 --- a/packages/graphql-language-service-server/src/GraphQLCache.ts +++ b/packages/graphql-language-service-server/src/GraphQLCache.ts @@ -31,6 +31,7 @@ import { parseDocument } from './parseDocument'; import stringToHash from './stringToHash'; import glob from 'glob'; import { LoadConfigOptions } from './types'; +import { fileURLToPath, pathToFileURL } from 'url'; // Maximum files to read when processing GraphQL files. const MAX_READS = 200; @@ -105,7 +106,7 @@ export class GraphQLCache implements GraphQLCacheInterface { getGraphQLConfig = (): GraphQLConfig => this._graphQLConfig; getProjectForFile = (uri: string): GraphQLProjectConfig => { - return this._graphQLConfig.getProjectForFile(uri.replace('file://', '')); + return this._graphQLConfig.getProjectForFile(fileURLToPath(uri)); }; getFragmentDependencies = async ( @@ -192,13 +193,7 @@ export class GraphQLCache implements GraphQLCacheInterface { return this._fragmentDefinitionsCache.get(rootDir) || new Map(); } - const filesFromInputDirs = await this._readFilesFromInputDirs( - rootDir, - projectConfig.documents, - ); - const list = filesFromInputDirs.filter(fileInfo => - projectConfig.match(fileInfo.filePath), - ); + const list = await this._readFilesFromInputDirs(rootDir, projectConfig); const { fragmentDefinitions, @@ -303,13 +298,7 @@ export class GraphQLCache implements GraphQLCacheInterface { if (this._typeDefinitionsCache.has(rootDir)) { return this._typeDefinitionsCache.get(rootDir) || new Map(); } - const filesFromInputDirs = await this._readFilesFromInputDirs( - rootDir, - projectConfig.documents, - ); - const list = filesFromInputDirs.filter(fileInfo => - projectConfig.match(fileInfo.filePath), - ); + const list = await this._readFilesFromInputDirs(rootDir, projectConfig); const { objectTypeDefinitions, graphQLFileMap, @@ -322,9 +311,10 @@ export class GraphQLCache implements GraphQLCacheInterface { _readFilesFromInputDirs = ( rootDir: string, - documents: GraphQLProjectConfig['documents'], + projectConfig: GraphQLProjectConfig, ): Promise> => { let pattern: string; + const { documents } = projectConfig; if (!documents || documents.length === 0) { return Promise.resolve([]); @@ -371,6 +361,7 @@ export class GraphQLCache implements GraphQLCacheInterface { .filter( filePath => typeof globResult.statCache[filePath] === 'object', ) + .filter(filePath => projectConfig.match(filePath)) .map(filePath => { // @TODO // so we have to force this here @@ -378,7 +369,7 @@ export class GraphQLCache implements GraphQLCacheInterface { // the docs indicate that is what's there :shrug: const cacheEntry = globResult.statCache[filePath] as fs.Stats; return { - filePath, + filePath: pathToFileURL(filePath).toString(), mtime: Math.trunc(cacheEntry.mtime.getTime() / 1000), size: cacheEntry.size, }; @@ -706,6 +697,7 @@ export class GraphQLCache implements GraphQLCacheInterface { const promises = chunk.map(fileInfo => this.promiseToReadGraphQLFile(fileInfo.filePath) .catch(error => { + 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 @@ -800,7 +792,7 @@ export class GraphQLCache implements GraphQLCacheInterface { */ promiseToReadGraphQLFile = (filePath: Uri): Promise => { return new Promise((resolve, reject) => - fs.readFile(filePath, 'utf8', (error, content) => { + fs.readFile(fileURLToPath(filePath), 'utf8', (error, content) => { if (error) { reject(error); return; diff --git a/packages/graphql-language-service-server/src/Logger.ts b/packages/graphql-language-service-server/src/Logger.ts index 56783c978e5..430881bf69d 100644 --- a/packages/graphql-language-service-server/src/Logger.ts +++ b/packages/graphql-language-service-server/src/Logger.ts @@ -71,8 +71,12 @@ export class Logger implements VSCodeLogger { const logMessage = `${timestamp} [${severity}] (pid: ${pid}) graphql-language-service-usage-logs: ${stringMessage}\n`; // write to the file in tmpdir fs.appendFile(this._logFilePath, logMessage, _error => {}); - this._getOutputStream(severity).write(logMessage, err => { - err && console.error(err); + const processSt = + severity === DIAGNOSTIC_SEVERITY.Error ? process.stderr : process.stdout; + processSt.write(logMessage, err => { + if (err) { + console.error(err); + } }); } diff --git a/packages/graphql-language-service-server/src/MessageProcessor.ts b/packages/graphql-language-service-server/src/MessageProcessor.ts index 06df44ee40f..26a1d3e27fe 100644 --- a/packages/graphql-language-service-server/src/MessageProcessor.ts +++ b/packages/graphql-language-service-server/src/MessageProcessor.ts @@ -9,7 +9,7 @@ import mkdirp from 'mkdirp'; import { readFileSync, existsSync, writeFileSync, writeFile } from 'fs'; -import { URL } from 'url'; +import { fileURLToPath, pathToFileURL } from 'url'; import * as path from 'path'; import { CachedContent, @@ -128,7 +128,7 @@ export class MessageProcessor { }; this._tmpDir = tmpDir || tmpdir(); this._tmpDirBase = path.join(this._tmpDir, 'graphql-language-service'); - this._tmpUriBase = path.join('file://', this._tmpDirBase); + this._tmpUriBase = pathToFileURL(this._tmpDirBase).toString(); this._loadConfigOptions = loadConfigOptions; if ( loadConfigOptions.extensions && @@ -577,7 +577,7 @@ export class MessageProcessor { ) { const uri = change.uri; - const text: string = readFileSync(new URL(uri).pathname).toString(); + const text = readFileSync(fileURLToPath(uri), { encoding: 'utf8' }); const contents = this._parser(text, uri); await this._updateFragmentDefinition(uri, contents); @@ -713,18 +713,8 @@ export class MessageProcessor { ); } } - return { - // TODO: fix this hack! - // URI is being misused all over this library - there's a link that - // defines how an URI should be structured: - // https://tools.ietf.org/html/rfc3986 - // Remove the below hack once the usage of URI is sorted out in related - // libraries. - uri: - res.path.indexOf('file:///') === 0 - ? res.path - : `file://${path.resolve(res.path)}`, + uri: res.path, range: defRange, } as Location; }) @@ -838,7 +828,9 @@ export class MessageProcessor { const isFileUri = existsSync(uri); let version = 1; if (isFileUri) { - const schemaUri = 'file://' + path.join(project.dirpath, uri); + const schemaUri = pathToFileURL( + path.join(project.dirpath, uri), + ).toString(); const schemaDocument = this._getCachedDocument(schemaUri); if (schemaDocument) { @@ -864,11 +856,12 @@ export class MessageProcessor { projectTmpPath = path.join(projectTmpPath, appendPath); } if (prependWithProtocol) { - return `file://${path.resolve(projectTmpPath)}`; + return pathToFileURL(path.resolve(projectTmpPath)).toString(); } else { return path.resolve(projectTmpPath); } } + async _cacheSchemaFilesForProject(project: GraphQLProjectConfig) { const schema = project?.schema; const config = project?.extensions?.languageService; @@ -967,8 +960,15 @@ export class MessageProcessor { if (!document.location || !document.rawSDL) { return; } + + let filePath = document.location; + if (!path.isAbsolute(filePath)) { + filePath = path.join(project.dirpath, document.location); + } + // build full system URI path with protocol - const uri = 'file://' + path.join(project.dirpath, document.location); + const uri = pathToFileURL(filePath).toString(); + // i would use the already existing graphql-config AST, but there are a few reasons we can't yet const contents = this._parser(document.rawSDL, uri); if (!contents[0] || !contents[0].query) { @@ -1015,11 +1015,7 @@ export class MessageProcessor { ): Promise { const rootDir = this._graphQLCache.getGraphQLConfig().dirpath; - await this._graphQLCache.updateFragmentDefinition( - rootDir, - new URL(uri).pathname, - contents, - ); + await this._graphQLCache.updateFragmentDefinition(rootDir, uri, contents); } async _updateObjectTypeDefinition( @@ -1028,11 +1024,7 @@ export class MessageProcessor { ): Promise { const rootDir = this._graphQLCache.getGraphQLConfig().dirpath; - await this._graphQLCache.updateObjectTypeDefinition( - rootDir, - new URL(uri).pathname, - contents, - ); + await this._graphQLCache.updateObjectTypeDefinition(rootDir, uri, contents); } _getCachedDocument(uri: string): CachedDocumentType | null { 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 9b67ff74236..7b918e5835a 100644 --- a/packages/graphql-language-service-server/src/__tests__/MessageProcessor-test.ts +++ b/packages/graphql-language-service-server/src/__tests__/MessageProcessor-test.ts @@ -22,6 +22,7 @@ import { loadConfig } from 'graphql-config'; import type { DefinitionQueryResult, Outline } from 'graphql-language-service'; import { Logger } from '../Logger'; +import { pathToFileURL } from 'url'; const baseConfig = { dirpath: __dirname }; @@ -36,7 +37,7 @@ describe('MessageProcessor', () => { loadConfigOptions: { rootDir: __dirname }, }); - const queryDir = `${__dirname}/__queries__`; + const queryPathUri = pathToFileURL(`${__dirname}/__queries__`); const textDocumentTestString = ` { hero(episode: NEWHOPE){ @@ -123,7 +124,7 @@ describe('MessageProcessor', () => { const initialDocument = { textDocument: { text: textDocumentTestString, - uri: `${queryDir}/test.graphql`, + uri: `${queryPathUri}/test.graphql`, version: 0, }, }; @@ -146,7 +147,7 @@ describe('MessageProcessor', () => { }); it('runs completion requests properly', async () => { - const uri = `${queryDir}/test2.graphql`; + const uri = `${queryPathUri}/test2.graphql`; const query = 'test'; messageProcessor._textDocumentCache.set(uri, { version: 0, @@ -170,7 +171,7 @@ describe('MessageProcessor', () => { }); it('runs document symbol requests', async () => { - const uri = `${queryDir}/test3.graphql`; + const uri = `${queryPathUri}/test3.graphql`; const validQuery = ` { hero(episode: EMPIRE){ @@ -214,7 +215,7 @@ describe('MessageProcessor', () => { }); it('properly changes the file cache with the didChange handler', async () => { - const uri = `file://${queryDir}/test.graphql`; + const uri = `${queryPathUri}/test.graphql`; messageProcessor._textDocumentCache.set(uri, { version: 1, contents: [ @@ -284,7 +285,7 @@ describe('MessageProcessor', () => { const newDocument = { textDocument: { text: validQuery, - uri: `${queryDir}/test3.graphql`, + uri: `${queryPathUri}/test3.graphql`, version: 1, }, }; @@ -306,7 +307,7 @@ describe('MessageProcessor', () => { }; const result = await messageProcessor.handleDefinitionRequest(test); - await expect(result[0].uri).toEqual(`file://${queryDir}/test3.graphql`); + await expect(result[0].uri).toEqual(`${queryPathUri}/test3.graphql`); }); it('parseDocument finds queries in tagged templates', async () => {