diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index 25f67deb974f1..5ede6fec5ce58 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -183,7 +183,7 @@ export type LoggerEvent = | CompileSkipEvent | PipelineErrorEvent | TimingEvent - | AutoDepsDecorations; + | AutoDepsDecorationsEvent; export type CompileErrorEvent = { kind: 'CompileError'; @@ -220,7 +220,7 @@ export type TimingEvent = { kind: 'Timing'; measurement: PerformanceMeasure; }; -export type AutoDepsDecorations = { +export type AutoDepsDecorationsEvent = { kind: 'AutoDepsDecorations'; useEffectCallExpr: t.SourceLocation; decorations: Array; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts index 7eb17bcbcb4ef..275a1a91b154c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts @@ -380,7 +380,10 @@ function collectDepUsages( for (const [, block] of fnExpr.loweredFunc.func.body.blocks) { for (const instr of block.instructions) { - if (instr.value.kind === 'LoadLocal') { + if ( + instr.value.kind === 'LoadLocal' && + identifiers.has(instr.value.place.identifier.id) + ) { loadedDeps.add(instr.lvalue.identifier.id); } for (const place of eachInstructionOperand(instr)) { diff --git a/compiler/packages/react-forgive/client/src/colors.ts b/compiler/packages/react-forgive/client/src/colors.ts new file mode 100644 index 0000000000000..5d98b078085cd --- /dev/null +++ b/compiler/packages/react-forgive/client/src/colors.ts @@ -0,0 +1,73 @@ +type RGB = [number, number, number]; + +const int = Math.floor; + +export class Color { + constructor( + private r: number, + private g: number, + private b: number, + ) {} + + toAlphaString(a: number) { + return this.toCssString(a); + } + toString() { + return this.toCssString(1); + } + + /** + * Adjust the color by a multiplier to lighten (`> 1.0`) or darken (`< 1.0`) the color. Returns a new + * instance. + */ + adjusted(mult: number) { + const adjusted = Color.redistribute([ + this.r * mult, + this.g * mult, + this.b * mult, + ]); + return new Color(...adjusted); + } + + private toCssString(a: number) { + return `rgba(${this.r},${this.g},${this.b},${a})`; + } + /** + * Redistributes rgb, maintaing hue until its clamped. + * https://stackoverflow.com/a/141943 + */ + private static redistribute([r, g, b]: RGB): RGB { + const threshold = 255.999; + const max = Math.max(r, g, b); + if (max <= threshold) { + return [int(r), int(g), int(b)]; + } + const total = r + g + b; + if (total >= 3 * threshold) { + return [int(threshold), int(threshold), int(threshold)]; + } + const x = (3 * threshold - total) / (3 * max - total); + const gray = threshold - x * max; + return [int(gray + x * r), int(gray + x * g), int(gray + x * b)]; + } +} + +export const BLACK = new Color(0, 0, 0); +export const WHITE = new Color(255, 255, 255); + +const COLOR_POOL = [ + new Color(249, 65, 68), + new Color(243, 114, 44), + new Color(248, 150, 30), + new Color(249, 132, 74), + new Color(249, 199, 79), + new Color(144, 190, 109), + new Color(67, 170, 139), + new Color(77, 144, 142), + new Color(87, 117, 144), + new Color(39, 125, 161), +]; + +export function getColorFor(index: number): Color { + return COLOR_POOL[Math.abs(index) % COLOR_POOL.length]!; +} diff --git a/compiler/packages/react-forgive/client/src/extension.ts b/compiler/packages/react-forgive/client/src/extension.ts index 0a7ae394354de..01da3fa49131f 100644 --- a/compiler/packages/react-forgive/client/src/extension.ts +++ b/compiler/packages/react-forgive/client/src/extension.ts @@ -4,12 +4,30 @@ import * as vscode from 'vscode'; import { LanguageClient, LanguageClientOptions, - Position, + type Position, + RequestType, ServerOptions, TransportKind, } from 'vscode-languageclient/node'; +import {WHITE} from './colors'; let client: LanguageClient; +const inferredEffectDepDecoration = + vscode.window.createTextEditorDecorationType({ + backgroundColor: WHITE.toAlphaString(0.3), + }); + +type Range = [Position, Position]; +interface AutoDepsDecorationsParams { + position: Position; +} +namespace AutoDepsDecorationsRequest { + export const type = new RequestType< + AutoDepsDecorationsParams, + Array | null, + void + >('react/autodeps_decorations'); +} export function activate(context: vscode.ExtensionContext) { const serverModule = context.asAbsolutePath(path.join('dist', 'server.js')); @@ -54,23 +72,24 @@ export function activate(context: vscode.ExtensionContext) { vscode.languages.registerHoverProvider(documentSelector, { provideHover(_document, position, _token) { client - .sendRequest('react/autodepsdecorations', position) - .then((decorations: Array<[Position, Position]>) => { - for (const [start, end] of decorations) { - const range = new vscode.Range( - new vscode.Position(start.line, start.character), - new vscode.Position(end.line, end.character), + .sendRequest(AutoDepsDecorationsRequest.type, {position}) + .then(decorations => { + if (Array.isArray(decorations)) { + const decorationOptions = decorations.map(([start, end]) => { + return { + range: new vscode.Range( + new vscode.Position(start.line, start.character), + new vscode.Position(end.line, end.character), + ), + hoverMessage: 'Inferred as an effect dependency', + }; + }); + vscode.window.activeTextEditor?.setDecorations( + inferredEffectDepDecoration, + decorationOptions, ); - const vscodeDecoration = - vscode.window.createTextEditorDecorationType({ - backgroundColor: 'red', - }); - vscode.window.activeTextEditor?.setDecorations(vscodeDecoration, [ - { - range, - hoverMessage: 'hehe', - }, - ]); + } else { + clearDecorations(inferredEffectDepDecoration); } }); return null; @@ -85,4 +104,11 @@ export function deactivate(): Thenable | undefined { if (client !== undefined) { return client.stop(); } + return; +} + +export function clearDecorations( + decorationType: vscode.TextEditorDecorationType, +) { + vscode.window.activeTextEditor?.setDecorations(decorationType, []); } diff --git a/compiler/packages/react-forgive/server/src/custom-requests/autodepsdecorations.ts b/compiler/packages/react-forgive/server/src/custom-requests/autodepsdecorations.ts deleted file mode 100644 index e05bac5c0fd5a..0000000000000 --- a/compiler/packages/react-forgive/server/src/custom-requests/autodepsdecorations.ts +++ /dev/null @@ -1,18 +0,0 @@ -import {AutoDepsDecorations} from 'babel-plugin-react-compiler/src/Entrypoint'; -import {Position} from 'vscode-languageserver-textdocument'; -import {sourceLocationToRange} from '../utils/lsp-adapter'; - -export type Range = [Position, Position]; -export type AutoDepsDecorationsLSPEvent = { - useEffectCallExpr: Range; - decorations: Array; -}; - -export function mapCompilerEventToLSPEvent( - event: AutoDepsDecorations, -): AutoDepsDecorationsLSPEvent { - return { - useEffectCallExpr: sourceLocationToRange(event.useEffectCallExpr), - decorations: event.decorations.map(sourceLocationToRange), - }; -} diff --git a/compiler/packages/react-forgive/server/src/index.ts b/compiler/packages/react-forgive/server/src/index.ts index c4b4f72c295ad..6432e3efc6aea 100644 --- a/compiler/packages/react-forgive/server/src/index.ts +++ b/compiler/packages/react-forgive/server/src/index.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {Position, TextDocument} from 'vscode-languageserver-textdocument'; +import {TextDocument} from 'vscode-languageserver-textdocument'; import { CodeLens, createConnection, @@ -19,15 +19,17 @@ import {compile, lastResult} from './compiler'; import {type PluginOptions} from 'babel-plugin-react-compiler/src'; import {resolveReactConfig} from './compiler/options'; import { - CompileSuccessEvent, + type CompileSuccessEvent, + type LoggerEvent, defaultOptions, - LoggerEvent, } from 'babel-plugin-react-compiler/src/Entrypoint/Options'; import {babelLocationToRange, getRangeFirstCharacter} from './compiler/compat'; import { - AutoDepsDecorationsLSPEvent, + type AutoDepsDecorationsLSPEvent, + AutoDepsDecorationsRequest, mapCompilerEventToLSPEvent, -} from './custom-requests/autodepsdecorations'; +} from './requests/autodepsdecorations'; +import {isPositionWithinRange} from './utils/range'; const SUPPORTED_LANGUAGE_IDS = new Set([ 'javascript', @@ -155,19 +157,11 @@ connection.onCodeLensResolve(lens => { return lens; }); -connection.onRequest('react/autodepsdecorations', (position: Position) => { - connection.console.log('Client hovering on: ' + JSON.stringify(position)); - connection.console.log(JSON.stringify(autoDepsDecorations, null, 2)); - +connection.onRequest(AutoDepsDecorationsRequest.type, async params => { + const position = params.position; + connection.console.debug('Client hovering on: ' + JSON.stringify(position)); for (const dec of autoDepsDecorations) { - // TODO: extract to helper - if ( - position.line >= dec.useEffectCallExpr[0].line && - position.line <= dec.useEffectCallExpr[1].line - ) { - connection.console.log( - 'found decoration: ' + JSON.stringify(dec.decorations), - ); + if (isPositionWithinRange(position, dec.useEffectCallExpr)) { return dec.decorations; } } diff --git a/compiler/packages/react-forgive/server/src/requests/autodepsdecorations.ts b/compiler/packages/react-forgive/server/src/requests/autodepsdecorations.ts new file mode 100644 index 0000000000000..43f4ac1fb9a2b --- /dev/null +++ b/compiler/packages/react-forgive/server/src/requests/autodepsdecorations.ts @@ -0,0 +1,28 @@ +import {type AutoDepsDecorationsEvent} from 'babel-plugin-react-compiler/src/Entrypoint'; +import {type Position} from 'vscode-languageserver-textdocument'; +import {RequestType} from 'vscode-languageserver/node'; +import {type Range, sourceLocationToRange} from '../utils/range'; + +export type AutoDepsDecorationsLSPEvent = { + useEffectCallExpr: Range; + decorations: Array; +}; +export interface AutoDepsDecorationsParams { + position: Position; +} +export namespace AutoDepsDecorationsRequest { + export const type = new RequestType< + AutoDepsDecorationsParams, + Array | null, + void + >('react/autodeps_decorations'); +} + +export function mapCompilerEventToLSPEvent( + event: AutoDepsDecorationsEvent, +): AutoDepsDecorationsLSPEvent { + return { + useEffectCallExpr: sourceLocationToRange(event.useEffectCallExpr), + decorations: event.decorations.map(sourceLocationToRange), + }; +} diff --git a/compiler/packages/react-forgive/server/src/utils/lsp-adapter.ts b/compiler/packages/react-forgive/server/src/utils/lsp-adapter.ts deleted file mode 100644 index 3a6b92320995b..0000000000000 --- a/compiler/packages/react-forgive/server/src/utils/lsp-adapter.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as t from '@babel/types'; -import {Position} from 'vscode-languageserver/node'; - -export function sourceLocationToRange( - loc: t.SourceLocation, -): [Position, Position] { - return [ - {line: loc.start.line - 1, character: loc.start.column}, - {line: loc.end.line - 1, character: loc.end.column}, - ]; -} diff --git a/compiler/packages/react-forgive/server/src/utils/range.ts b/compiler/packages/react-forgive/server/src/utils/range.ts new file mode 100644 index 0000000000000..e0665ba8fe8c2 --- /dev/null +++ b/compiler/packages/react-forgive/server/src/utils/range.ts @@ -0,0 +1,19 @@ +import * as t from '@babel/types'; +import {type Position} from 'vscode-languageserver/node'; + +export type Range = [Position, Position]; +export function isPositionWithinRange( + position: Position, + [start, end]: Range, +): boolean { + return position.line >= start.line && position.line <= end.line; +} + +export function sourceLocationToRange( + loc: t.SourceLocation, +): [Position, Position] { + return [ + {line: loc.start.line - 1, character: loc.start.column}, + {line: loc.end.line - 1, character: loc.end.column}, + ]; +} diff --git a/compiler/packages/react-forgive/tsconfig.json b/compiler/packages/react-forgive/tsconfig.json new file mode 100644 index 0000000000000..6aeffaeb354da --- /dev/null +++ b/compiler/packages/react-forgive/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "@tsconfig/strictest/tsconfig.json", + "compilerOptions": { + "module": "Node16", + "moduleResolution": "Node16", + "rootDir": "../", + "noEmit": true, + "jsx": "react-jsxdev", + "lib": ["ES2022"], + + "target": "ES2022", + "importsNotUsedAsValues": "remove", + }, + "exclude": ["node_modules"], + "include": ["server/src/**/*.ts", "client/src/**/*.ts"], +}