-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Summary: This PR aims to scaffold out an MVP for the Relay LSP VSCode Extension. ## High Level Changes - Added new `vscode-extension` in the root dir containing all the extension code. - We're using typescript in this new directory. Spoke this over with captbaritone and it seems like we're cool with it. - Main benefit is we get all the types published by the VSCode team for free without having to wait for published flow defs. - Added a basic LSP Client in `extension.ts` which looks for a relay binary and starts it up. Things seem to be working well in the Coinbase codebase. - Added a `launch.json` so contributors can easily test the extension with `F5`. It also lets you see the debug output from the LSP Server directly in the opened window. It's really sweet. ## Follow Ups I left a bunch of TODOs in the source if you want some more insight. - [ ] Use VSCode config instead of environment variables from the launch json - [ ] Support VSCode workspaces (don't use `workspace.rootPath`). Not exactly sure how to handle this but will be looking at the flow extension for more guidance. - [ ] Better error message handling - [ ] Status Bar support - [x] Fix go to definition on fields / types - [ ] Add command to manually restart the LSP Client. - [x] Add a Github Action to do type checking / build check / prettier / linting Pull Request resolved: #3858 Test Plan: Imported from GitHub, without a `Test Plan:` line. **Static Docs Preview: relay** |[Full Site](https://our.intern.facebook.com/intern/staticdocs/eph/D35408177/V5/relay/)| |**Modified Pages**| **Static Docs Preview: relay** |[Full Site](https://our.intern.facebook.com/intern/staticdocs/eph/D35408177/V6/relay/)| |**Modified Pages**| Reviewed By: alunyov Differential Revision: D35408177 Pulled By: captbaritone fbshipit-source-id: 26db9dcd0c6e03e955c4f07898d4737eae5322f7
- Loading branch information
1 parent
d80203a
commit 4c8a6df
Showing
12 changed files
with
1,734 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
{ | ||
"root": true, | ||
"ignorePatterns": "out/*", | ||
"parserOptions": { | ||
"project": "./tsconfig.json" | ||
}, | ||
"extends": ["airbnb-base", "airbnb-typescript/base"], | ||
"rules": { | ||
"operator-linebreak": "off", | ||
"@typescript-eslint/object-curly-spacing": "off", | ||
"import/prefer-default-export": "off", | ||
"no-await-in-loop": "off" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
out/ | ||
tsconfig.tsbuildinfo |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
{ | ||
"version": "0.2.0", | ||
"configurations": [ | ||
{ | ||
"name": "Relay Extension", | ||
"type": "extensionHost", | ||
"request": "launch", | ||
"runtimeExecutable": "${execPath}", | ||
"args": ["--extensionDevelopmentPath=${workspaceFolder}"], | ||
"preLaunchTask": "ts-build", | ||
"env": { | ||
// Use this if you want to use a local binary | ||
// Otherwise we'll look in your node modules | ||
// "RELAY_BINARY_PATH": "/path/to/your/binary", | ||
"RELAY_LSP_LOG_LEVEL": "debug" | ||
} | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"files.exclude": { | ||
"out": true | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
{ | ||
// See https://go.microsoft.com/fwlink/?LinkId=733558 | ||
// for the documentation about the tasks.json format | ||
"version": "2.0.0", | ||
"tasks": [ | ||
{ | ||
"label": "ts-build", | ||
"type": "typescript", | ||
"tsconfig": "tsconfig.json", | ||
"problemMatcher": ["$tsc"], | ||
"group": { | ||
"kind": "build", | ||
"isDefault": false | ||
} | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
{ | ||
"name": "relay", | ||
"displayName": "Relay GraphQL", | ||
"version": "0.0.1", | ||
"description": "Relay LSP Client for VSCode", | ||
"main": "./out/extension.js", | ||
"activationEvents": [ | ||
"onLanguage:plaintext", | ||
"onLanguage:javascript", | ||
"onLanguage:javascriptreact", | ||
"onLanguage:typescript", | ||
"onLanguage:typescriptreact" | ||
], | ||
"contributes": { | ||
"configuration": { | ||
"type": "object", | ||
"title": "Relay", | ||
"properties": { | ||
"relay.outputLevel": { | ||
"scope": "window", | ||
"type": "string", | ||
"default": "quiet-with-errors", | ||
"enum": [ | ||
"quiet", | ||
"quiet-with-errors", | ||
"verbose", | ||
"debug" | ||
], | ||
"description": "Controls what is logged to the Output Channel." | ||
}, | ||
"relay.pathToRelay": { | ||
"scope": "window", | ||
"type": "string", | ||
"description": "Absolute path to the relay binary. If not provided, the extension will look in the nearest node_modules directory" | ||
} | ||
} | ||
} | ||
}, | ||
"scripts": { | ||
"typecheck": "tsc --noEmit", | ||
"prettier-check": "prettier -c .", | ||
"lint": "eslint --max-warnings 0 ." | ||
}, | ||
"engines": { | ||
"vscode": "^1.65.0" | ||
}, | ||
"prettier": { | ||
"bracketSameLine": true, | ||
"bracketSpacing": false, | ||
"singleQuote": true, | ||
"trailingComma": "all" | ||
}, | ||
"dependencies": { | ||
"vscode-languageclient": "^7.0.0" | ||
}, | ||
"devDependencies": { | ||
"@types/node": "^17.0.23", | ||
"@types/vscode": "^1.65.0", | ||
"@typescript-eslint/eslint-plugin": "^5.13.0", | ||
"@typescript-eslint/parser": "^5.0.0", | ||
"eslint": "^8.12.0", | ||
"eslint-config-airbnb-base": "^15.0.0", | ||
"eslint-config-airbnb-typescript": "^17.0.0", | ||
"eslint-plugin-import": "^2.26.0", | ||
"prettier": "^2.6.2", | ||
"typescript": "^4.6.3" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
/** | ||
* Copyright (c) Meta Platforms, Inc. and affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
*/ | ||
|
||
import {workspace, window} from 'vscode'; | ||
|
||
import { | ||
CloseAction, | ||
ErrorAction, | ||
LanguageClient, | ||
LanguageClientOptions, | ||
RevealOutputChannelOn, | ||
ServerOptions, | ||
} from 'vscode-languageclient/node'; | ||
import {findRelayBinary} from './utils'; | ||
|
||
let client: LanguageClient; | ||
|
||
export async function activate() { | ||
const outputChannel = window.createOutputChannel('Relay Language Server'); | ||
|
||
// TODO: Support multi folder workspaces by not using rootPath. | ||
// Maybe initialize a client once for each workspace? | ||
const relayBinary = | ||
// TODO: Use VSCode config instead of process.env | ||
process.env.RELAY_BINARY_PATH ?? | ||
(await findRelayBinary(workspace.rootPath)); | ||
|
||
// TODO: Use VSCode config instead of process.env | ||
const outputLevel = process.env.RELAY_LSP_LOG_LEVEL ?? 'debug'; | ||
|
||
if (!relayBinary) { | ||
outputChannel.appendLine( | ||
"Could not find relay binary in path. Maybe you're not inside of a project with relay installed.", | ||
); | ||
|
||
return; | ||
} | ||
|
||
outputChannel.appendLine(`Using relay binary: ${relayBinary}`); | ||
|
||
const serverOptions: ServerOptions = { | ||
command: relayBinary, | ||
args: ['lsp', `--output=${outputLevel}`], | ||
}; | ||
|
||
// Options to control the language client | ||
const clientOptions: LanguageClientOptions = { | ||
markdown: { | ||
isTrusted: true, | ||
}, | ||
documentSelector: [ | ||
{scheme: 'file', language: 'javascript'}, | ||
{scheme: 'file', language: 'typescript'}, | ||
{scheme: 'file', language: 'typescriptreact'}, | ||
{scheme: 'file', language: 'javascriptreact'}, | ||
], | ||
|
||
outputChannel, | ||
|
||
// Since we use stderr for debug logs, the "Something went wrong" popup | ||
// in VSCode shows up a lot. This tells vscode not to show it in any case. | ||
revealOutputChannelOn: RevealOutputChannelOn.Never, | ||
|
||
initializationFailedHandler: (error) => { | ||
outputChannel.appendLine(`initializationFailedHandler ${error}`); | ||
|
||
return true; | ||
}, | ||
|
||
errorHandler: { | ||
// This happens when the LSP server stops running. | ||
// e.g. Could not find relay config. | ||
// e.g. watchman was not installed. | ||
// | ||
// TODO: Figure out the best way to handle this `closed` event | ||
// | ||
// Some of these messages are worth surfacing and others are not | ||
// e.g. "Watchman is not installed" is important to surface to the user | ||
// but "No relay config found" is not relevant since the user is likely | ||
// just in a workspace where they don't have a relay config. | ||
// | ||
// We already bail early if there is no relay binary found. | ||
// So maybe we should just show all of these messages since it would | ||
// be weird if you had a relay binary in your node modules but no relay | ||
// config could be found. 🤷 for now. | ||
closed() { | ||
window | ||
.showWarningMessage( | ||
'Relay LSP client connection got closed unexpectedly.', | ||
'Go to output', | ||
'Ignore', | ||
) | ||
.then((selected) => { | ||
if (selected === 'Go to output') { | ||
client.outputChannel.show(); | ||
} | ||
}); | ||
|
||
return CloseAction.DoNotRestart; | ||
}, | ||
// This `error` callback should probably never happen. 🙏 | ||
error() { | ||
window | ||
.showWarningMessage( | ||
'An error occurred while writing/reading to/from the relay lsp connection', | ||
'Go to output', | ||
'Ignore', | ||
) | ||
.then((selected) => { | ||
if (selected === 'Go to output') { | ||
client.outputChannel.show(); | ||
} | ||
}); | ||
|
||
return ErrorAction.Continue; | ||
}, | ||
}, | ||
}; | ||
|
||
// Create the language client and start the client. | ||
client = new LanguageClient( | ||
'RelayLanguageClient', | ||
'Relay Language Client', | ||
serverOptions, | ||
clientOptions, | ||
); | ||
|
||
// Start the client. This will also launch the server | ||
client.start(); | ||
} | ||
|
||
export function deactivate(): Thenable<void> | undefined { | ||
if (!client) { | ||
return undefined; | ||
} | ||
return client.stop(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
/** | ||
* Copyright (c) Meta Platforms, Inc. and affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
*/ | ||
|
||
import * as path from 'path'; | ||
import * as fs from 'fs/promises'; | ||
|
||
async function exists(file: string): Promise<boolean> { | ||
return fs | ||
.stat(file) | ||
.then(() => true) | ||
.catch(() => false); | ||
} | ||
|
||
// This is derived from the relay-compiler npm package. | ||
// If you update this, please update accordingly here | ||
// https://github.com/facebook/relay/blob/main/packages/relay-compiler/index.js | ||
function getBinaryPathRelativeToPackageJson() { | ||
let binaryPathRelativeToPackageJson; | ||
if (process.platform === 'darwin' && process.arch === 'x64') { | ||
binaryPathRelativeToPackageJson = path.join('macos-x64', 'relay'); | ||
} else if (process.platform === 'darwin' && process.arch === 'arm64') { | ||
binaryPathRelativeToPackageJson = path.join('macos-arm64', 'relay'); | ||
} else if (process.platform === 'linux' && process.arch === 'x64') { | ||
binaryPathRelativeToPackageJson = path.join('linux-x64', 'relay'); | ||
} else if (process.platform === 'win32' && process.arch === 'x64') { | ||
binaryPathRelativeToPackageJson = path.join('win-x64', 'relay.exe'); | ||
} else { | ||
binaryPathRelativeToPackageJson = null; | ||
} | ||
|
||
if (binaryPathRelativeToPackageJson) { | ||
return path.join( | ||
'.', | ||
'node_modules', | ||
'relay-compiler', | ||
binaryPathRelativeToPackageJson, | ||
); | ||
} | ||
|
||
return null; | ||
} | ||
|
||
export async function findRelayBinary( | ||
rootPath: string, | ||
): Promise<string | null> { | ||
const binaryPathRelativeToPackageJson = getBinaryPathRelativeToPackageJson(); | ||
|
||
let counter = 0; | ||
let currentPath = rootPath; | ||
|
||
// eslint-disable-next-line no-constant-condition | ||
while (true) { | ||
if (counter >= 5000) { | ||
throw new Error( | ||
'Could not find Relay binary after 5000 traversals. This is likely a bug in the extension code and should be reported to https://github.com/facebook/relay/issues', | ||
); | ||
} | ||
|
||
counter += 1; | ||
|
||
const possibleBinaryPath = path.join( | ||
currentPath, | ||
binaryPathRelativeToPackageJson, | ||
); | ||
|
||
if (await exists(possibleBinaryPath)) { | ||
return possibleBinaryPath; | ||
} | ||
|
||
const nextPath = path.normalize(path.join(currentPath, '..')); | ||
|
||
// Eventually we'll get to `/` and get stuck in a loop. | ||
if (nextPath === currentPath) { | ||
break; | ||
} else { | ||
currentPath = nextPath; | ||
} | ||
} | ||
|
||
return null; | ||
} |
Oops, something went wrong.