Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -176,41 +176,48 @@ export type CompilationMode = z.infer<typeof CompilationModeSchema>;
* babel or other unhandled exceptions).
*/
export type LoggerEvent =
| {
kind: 'CompileError';
fnLoc: t.SourceLocation | null;
detail: CompilerErrorDetailOptions;
}
| {
kind: 'CompileDiagnostic';
fnLoc: t.SourceLocation | null;
detail: Omit<Omit<CompilerErrorDetailOptions, 'severity'>, 'suggestions'>;
}
| {
kind: 'CompileSkip';
fnLoc: t.SourceLocation | null;
reason: string;
loc: t.SourceLocation | null;
}
| {
kind: 'CompileSuccess';
fnLoc: t.SourceLocation | null;
fnName: string | null;
memoSlots: number;
memoBlocks: number;
memoValues: number;
prunedMemoBlocks: number;
prunedMemoValues: number;
}
| {
kind: 'PipelineError';
fnLoc: t.SourceLocation | null;
data: string;
}
| {
kind: 'Timing';
measurement: PerformanceMeasure;
};
| CompileSuccessEvent
| CompileErrorEvent
| CompileDiagnosticEvent
| CompileSkipEvent
| PipelineErrorEvent
| TimingEvent;

export type CompileErrorEvent = {
kind: 'CompileError';
fnLoc: t.SourceLocation | null;
detail: CompilerErrorDetailOptions;
};
export type CompileDiagnosticEvent = {
kind: 'CompileDiagnostic';
fnLoc: t.SourceLocation | null;
detail: Omit<Omit<CompilerErrorDetailOptions, 'severity'>, 'suggestions'>;
};
export type CompileSuccessEvent = {
kind: 'CompileSuccess';
fnLoc: t.SourceLocation | null;
fnName: string | null;
memoSlots: number;
memoBlocks: number;
memoValues: number;
prunedMemoBlocks: number;
prunedMemoValues: number;
};
export type CompileSkipEvent = {
kind: 'CompileSkip';
fnLoc: t.SourceLocation | null;
reason: string;
loc: t.SourceLocation | null;
};
export type PipelineErrorEvent = {
kind: 'PipelineError';
fnLoc: t.SourceLocation | null;
data: string;
};
export type TimingEvent = {
kind: 'Timing';
measurement: PerformanceMeasure;
};

export type Logger = {
logEvent: (filename: string | null, event: LoggerEvent) => void;
Expand Down
22 changes: 22 additions & 0 deletions compiler/packages/react-forgive/server/src/compiler/compat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {SourceLocation} from 'babel-plugin-react-compiler/src';
import {type Range} from 'vscode-languageserver';

export function babelLocationToRange(loc: SourceLocation): Range | null {
if (typeof loc === 'symbol') {
return null;
}
return {
start: {line: loc.start.line - 1, character: loc.start.column},
end: {line: loc.end.line - 1, character: loc.end.column},
};
}

/**
* Refine range to only the first character.
*/
export function getRangeFirstCharacter(range: Range): Range {
return {
start: range.start,
end: range.start,
};
}
12 changes: 10 additions & 2 deletions compiler/packages/react-forgive/server/src/compiler/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ import BabelPluginReactCompiler, {
type PluginOptions,
} from 'babel-plugin-react-compiler/src';
import * as babelParser from 'prettier/plugins/babel.js';
import * as estreeParser from 'prettier/plugins/estree';
import estreeParser from 'prettier/plugins/estree';
import * as typescriptParser from 'prettier/plugins/typescript';
import * as prettier from 'prettier/standalone';

export let lastResult: BabelCore.BabelFileResult | null = null;

type CompileOptions = {
text: string;
file: string;
Expand All @@ -24,14 +26,17 @@ export async function compile({
text,
file,
options,
}: CompileOptions): Promise<BabelCore.BabelFileResult> {
}: CompileOptions): Promise<BabelCore.BabelFileResult | null> {
const ast = await parseAsync(text, {
sourceFileName: file,
parserOpts: {
plugins: ['typescript', 'jsx'],
},
sourceType: 'module',
});
if (ast == null) {
return null;
}
const plugins =
options != null
? [[BabelPluginReactCompiler, options]]
Expand All @@ -54,5 +59,8 @@ export async function compile({
parser: 'babel-ts',
plugins: [babelParser, estreeParser, typescriptParser],
});
if (result.code != null) {
lastResult = result;
}
return result;
}
80 changes: 55 additions & 25 deletions compiler/packages/react-forgive/server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,23 @@

import {TextDocument} from 'vscode-languageserver-textdocument';
import {
CodeLens,
createConnection,
type InitializeParams,
type InitializeResult,
ProposedFeatures,
TextDocuments,
TextDocumentSyncKind,
} from 'vscode-languageserver/node';
import {compile} from './compiler';
import {compile, lastResult} from './compiler';
import {type PluginOptions} from 'babel-plugin-react-compiler/src';
import {resolveReactConfig} from './compiler/options';
import {type BabelFileResult} from '@babel/core';
import {
CompileSuccessEvent,
defaultOptions,
LoggerEvent,
} from 'babel-plugin-react-compiler/src/Entrypoint/Options';
import {babelLocationToRange, getRangeFirstCharacter} from './compiler/compat';

const SUPPORTED_LANGUAGE_IDS = new Set([
'javascript',
Expand All @@ -30,11 +36,21 @@ const connection = createConnection(ProposedFeatures.all);
const documents = new TextDocuments(TextDocument);

let compilerOptions: PluginOptions | null = null;
let lastResult: BabelFileResult | null = null;
let compiledFns: Set<CompileSuccessEvent> = new Set();

connection.onInitialize((_params: InitializeParams) => {
// TODO(@poteto) get config fr
compilerOptions = resolveReactConfig('.');
compilerOptions = resolveReactConfig('.') ?? defaultOptions;
compilerOptions = {
...compilerOptions,
logger: {
logEvent(_filename: string | null, event: LoggerEvent) {
if (event.kind === 'CompileSuccess') {
compiledFns.add(event);
}
},
},
};
const result: InitializeResult = {
capabilities: {
textDocumentSync: TextDocumentSyncKind.Full,
Expand All @@ -48,44 +64,58 @@ connection.onInitialized(() => {
connection.console.log('initialized');
});

documents.onDidOpen(async event => {
if (SUPPORTED_LANGUAGE_IDS.has(event.document.languageId)) {
const text = event.document.getText();
const result = await compile({
text,
file: event.document.uri,
options: compilerOptions,
});
if (result.code != null) {
lastResult = result;
}
}
});

documents.onDidChangeContent(async event => {
connection.console.info(`Changed: ${event.document.uri}`);
compiledFns.clear();
if (SUPPORTED_LANGUAGE_IDS.has(event.document.languageId)) {
const text = event.document.getText();
const result = await compile({
await compile({
text,
file: event.document.uri,
options: compilerOptions,
});
if (result.code != null) {
lastResult = result;
}
}
});

connection.onDidChangeWatchedFiles(change => {
compiledFns.clear();
connection.console.log(
change.changes.map(c => `File changed: ${c.uri}`).join('\n'),
);
});

connection.onCodeLens(params => {
connection.console.log('lastResult: ' + JSON.stringify(lastResult, null, 2));
connection.console.log('params: ' + JSON.stringify(params, null, 2));
return [];
connection.console.info(`Handling codelens for: ${params.textDocument.uri}`);
if (compiledFns.size === 0) {
return;
}
const lenses: Array<CodeLens> = [];
for (const compiled of compiledFns) {
if (compiled.fnLoc != null) {
const fnLoc = babelLocationToRange(compiled.fnLoc);
if (fnLoc === null) continue;
const lens = CodeLens.create(
getRangeFirstCharacter(fnLoc),
compiled.fnLoc,
);
if (lastResult?.code != null) {
lens.command = {
title: 'Optimized by React Compiler',
command: 'todo',
};
}
lenses.push(lens);
}
}
return lenses;
});

connection.onCodeLensResolve(lens => {
connection.console.info(`Resolving codelens for: ${JSON.stringify(lens)}`);
if (lastResult?.code != null) {
connection.console.log(lastResult.code);
}
return lens;
});

documents.listen(connection);
Expand Down
Loading