Skip to content

Commit b9eefa6

Browse files
committed
[forgive] Init
Init basic LSP. At the moment the extension doesn't do anything interesting, but it does compile successfully. Summary: Test Plan: Reviewers: Subscribers: Tasks: Tags: Summary: Test Plan: Reviewers: Subscribers: Tasks: Tags:
1 parent c59a6f2 commit b9eefa6

File tree

16 files changed

+1183
-198
lines changed

16 files changed

+1183
-198
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,4 @@ packages/react-devtools-fusebox/dist
3737
packages/react-devtools-inline/dist
3838
packages/react-devtools-shell/dist
3939
packages/react-devtools-timeline/dist
40+

compiler/.gitignore

+5-1
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,8 @@ dist
2121
.spr.yml
2222
testfilter.txt
2323

24-
bundle-oss.sh
24+
bundle-oss.sh
25+
26+
# forgive
27+
*.vsix
28+
.vscode-test
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import {defineConfig} from '@vscode/test-cli';
2+
3+
export default defineConfig({files: 'dist/test/**/*.test.js'});
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ignore-engines true
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) Meta Platforms, Inc. and affiliates.
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

compiler/packages/react-forgive/client/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"repository": {
1212
"type": "git",
1313
"url": "git+https://github.com/facebook/react.git",
14-
"directory": "compiler/packages/react-forgive-client"
14+
"directory": "compiler/packages/react-forgive"
1515
},
1616
"dependencies": {
1717
"vscode-languageclient": "^9.0.1"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import * as path from 'path';
2+
import {ExtensionContext, window as Window} from 'vscode';
3+
4+
import {
5+
LanguageClient,
6+
LanguageClientOptions,
7+
ServerOptions,
8+
TransportKind,
9+
} from 'vscode-languageclient/node';
10+
11+
let client: LanguageClient;
12+
13+
export function activate(context: ExtensionContext) {
14+
const serverModule = context.asAbsolutePath(path.join('dist', 'server.js'));
15+
16+
// If the extension is launched in debug mode then the debug server options are used
17+
// Otherwise the run options are used
18+
const serverOptions: ServerOptions = {
19+
run: {
20+
module: serverModule,
21+
transport: TransportKind.ipc,
22+
},
23+
debug: {
24+
module: serverModule,
25+
transport: TransportKind.ipc,
26+
},
27+
};
28+
29+
const clientOptions: LanguageClientOptions = {
30+
documentSelector: [
31+
{scheme: 'file', language: 'javascriptreact'},
32+
{scheme: 'file', language: 'typescriptreact'},
33+
],
34+
progressOnInitialization: true,
35+
};
36+
37+
// Create the language client and start the client.
38+
try {
39+
client = new LanguageClient(
40+
'react-forgive',
41+
'React Analyzer',
42+
serverOptions,
43+
clientOptions,
44+
);
45+
} catch {
46+
Window.showErrorMessage(
47+
`React Analyzer couldn't be started. See the output channel for details.`,
48+
);
49+
return;
50+
}
51+
52+
client.registerProposedFeatures();
53+
client.start();
54+
}
55+
56+
export function deactivate(): Thenable<void> | undefined {
57+
if (client !== undefined) {
58+
return client.stop();
59+
}
60+
}

compiler/packages/react-forgive/package.json

+6-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
],
2020
"publisher": "Meta",
2121
"engines": {
22-
"vscode": "^1.75.0"
22+
"vscode": "^1.96.0"
2323
},
2424
"activationEvents": [
2525
"onLanguage:javascriptreact",
@@ -49,8 +49,13 @@
4949
},
5050
"devDependencies": {
5151
"@eslint/js": "^9.13.0",
52+
"@types/mocha": "^10.0.10",
5253
"@types/node": "^20",
54+
"@types/vscode": "^1.96.0",
55+
"@vscode/test-cli": "^0.0.10",
56+
"@vscode/test-electron": "^2.4.1",
5357
"eslint": "^9.13.0",
58+
"mocha": "^11.0.1",
5459
"typescript-eslint": "^8.16.0",
5560
"yargs": "^17.7.2"
5661
}

compiler/packages/react-forgive/scripts/server.mjs

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const __filename = fileURLToPath(import.meta.url);
1212
const __dirname = path.dirname(__filename);
1313

1414
export const entryPoint = path.join(__dirname, '../server/src/index.ts');
15-
export const outfile = path.join(__dirname, '../dist/extension.js');
15+
export const outfile = path.join(__dirname, '../dist/server.js');
1616
export const config = {
1717
entryPoints: [entryPoint],
1818
outfile,

compiler/packages/react-forgive/server/package.json

+8-2
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,21 @@
55
"description": "Experimental LSP server",
66
"license": "MIT",
77
"scripts": {
8-
"build": "echo 'no build'",
8+
"build": "rimraf dist && rollup --config --bundleConfigAsCjs",
99
"test": "echo 'no tests'"
1010
},
1111
"repository": {
1212
"type": "git",
1313
"url": "git+https://github.com/facebook/react.git",
14-
"directory": "compiler/packages/react-forgive-server"
14+
"directory": "compiler/packages/react-forgive"
1515
},
1616
"dependencies": {
17+
"@babel/core": "^7.26.0",
18+
"@babel/parser": "^7.26.0",
19+
"@babel/plugin-syntax-typescript": "^7.25.9",
20+
"@babel/types": "^7.26.0",
21+
"cosmiconfig": "^9.0.0",
22+
"prettier": "^3.3.3",
1723
"vscode-languageserver": "^9.0.1",
1824
"vscode-languageserver-textdocument": "^1.0.12"
1925
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import type * as BabelCore from '@babel/core';
9+
import {parseAsync, transformFromAstAsync} from '@babel/core';
10+
import BabelPluginReactCompiler, {
11+
type PluginOptions,
12+
} from 'babel-plugin-react-compiler/src';
13+
import * as babelParser from 'prettier/plugins/babel.js';
14+
import * as estreeParser from 'prettier/plugins/estree';
15+
import * as typescriptParser from 'prettier/plugins/typescript';
16+
import * as prettier from 'prettier/standalone';
17+
18+
type CompileOptions = {
19+
text: string;
20+
file: string;
21+
options: PluginOptions | null;
22+
};
23+
export async function compile({
24+
text,
25+
file,
26+
options,
27+
}: CompileOptions): Promise<BabelCore.BabelFileResult> {
28+
const ast = await parseAsync(text, {
29+
sourceFileName: file,
30+
parserOpts: {
31+
plugins: ['typescript', 'jsx'],
32+
},
33+
sourceType: 'module',
34+
});
35+
const plugins =
36+
options != null
37+
? [[BabelPluginReactCompiler, options]]
38+
: [[BabelPluginReactCompiler]];
39+
const result = await transformFromAstAsync(ast, text, {
40+
filename: file,
41+
highlightCode: false,
42+
retainLines: true,
43+
plugins,
44+
sourceType: 'module',
45+
sourceFileName: file,
46+
});
47+
if (result?.code == null) {
48+
throw new Error(
49+
`Expected BabelPluginReactCompiler to compile successfully, got ${result}`,
50+
);
51+
}
52+
result.code = await prettier.format(result.code, {
53+
semi: false,
54+
parser: 'babel-ts',
55+
plugins: [babelParser, estreeParser, typescriptParser],
56+
});
57+
return result;
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import {
9+
parsePluginOptions,
10+
type PluginOptions,
11+
} from 'babel-plugin-react-compiler/src';
12+
import {cosmiconfigSync} from 'cosmiconfig';
13+
14+
export function resolveReactConfig(projectPath: string): PluginOptions | null {
15+
const explorerSync = cosmiconfigSync('react', {
16+
searchStrategy: 'project',
17+
cache: true,
18+
});
19+
const result = explorerSync.search(projectPath);
20+
if (result != null) {
21+
return parsePluginOptions(result.config);
22+
} else {
23+
return null;
24+
}
25+
}

compiler/packages/react-forgive/server/src/index.ts

+87
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,90 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*/
7+
8+
import {TextDocument} from 'vscode-languageserver-textdocument';
9+
import {
10+
createConnection,
11+
type InitializeParams,
12+
type InitializeResult,
13+
ProposedFeatures,
14+
TextDocuments,
15+
TextDocumentSyncKind,
16+
} from 'vscode-languageserver/node';
17+
import {compile} from './compiler';
18+
import {type PluginOptions} from 'babel-plugin-react-compiler/src';
19+
import {resolveReactConfig} from './compiler/options';
20+
import {type BabelFileResult} from '@babel/core';
21+
22+
const SUPPORTED_LANGUAGE_IDS = new Set([
23+
'javascript',
24+
'javascriptreact',
25+
'typescript',
26+
'typescriptreact',
27+
]);
28+
29+
const connection = createConnection(ProposedFeatures.all);
30+
const documents = new TextDocuments(TextDocument);
31+
32+
let compilerOptions: PluginOptions | null = null;
33+
let lastResult: BabelFileResult | null = null;
34+
35+
connection.onInitialize((_params: InitializeParams) => {
36+
// TODO(@poteto) get config fr
37+
compilerOptions = resolveReactConfig('.');
38+
const result: InitializeResult = {
39+
capabilities: {
40+
textDocumentSync: TextDocumentSyncKind.Full,
41+
codeLensProvider: {resolveProvider: true},
42+
},
43+
};
44+
return result;
45+
});
46+
47+
connection.onInitialized(() => {
48+
connection.console.log('initialized');
49+
});
50+
51+
documents.onDidOpen(async event => {
52+
if (SUPPORTED_LANGUAGE_IDS.has(event.document.languageId)) {
53+
const text = event.document.getText();
54+
const result = await compile({
55+
text,
56+
file: event.document.uri,
57+
options: compilerOptions,
58+
});
59+
if (result.code != null) {
60+
lastResult = result;
61+
}
62+
}
63+
});
64+
65+
documents.onDidChangeContent(async event => {
66+
if (SUPPORTED_LANGUAGE_IDS.has(event.document.languageId)) {
67+
const text = event.document.getText();
68+
const result = await compile({
69+
text,
70+
file: event.document.uri,
71+
options: compilerOptions,
72+
});
73+
if (result.code != null) {
74+
lastResult = result;
75+
}
76+
}
77+
});
78+
79+
connection.onDidChangeWatchedFiles(change => {
80+
connection.console.log(
81+
change.changes.map(c => `File changed: ${c.uri}`).join('\n'),
82+
);
83+
});
84+
85+
connection.onCodeLens(params => {
86+
connection.console.log('lastResult: ' + JSON.stringify(lastResult, null, 2));
87+
connection.console.log('params: ' + JSON.stringify(params, null, 2));
88+
return [];
89+
});
90+
91+
documents.listen(connection);
92+
connection.listen();
93+
connection.console.info(`React Analyzer running in node ${process.version}`);
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
{
22
"extends": "@tsconfig/strictest/tsconfig.json",
33
"compilerOptions": {
4-
"module": "CommonJS",
5-
"moduleResolution": "node",
6-
"outDir": "dist",
4+
"module": "ES2015",
5+
"moduleResolution": "Bundler",
6+
"rootDir": "../../..",
7+
"noEmit": true,
78
"jsx": "react-jsxdev",
8-
"lib": ["ES2020"],
9-
"target": "ES2020",
9+
"target": "ES2015",
10+
"sourceMap": false,
11+
"removeComments": true,
1012
},
1113
"exclude": ["node_modules", ".vscode-test"],
12-
"include": ["src/**/*.ts"],
14+
"include": ["src/**/*.ts"]
1315
}

0 commit comments

Comments
 (0)