@samitouri / QOS-React-2 / commits / 7b21c46489

[forgive] Refactor inferred deps (#32999)

Refactor. Co-authored-by: Jordan Brown <jmbrown@meta.com> --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32999). * #33002 * #33001 * #33000 * __->__ #32999 Co-authored-by: Jordan Brown <jmbrown@meta.com>

lauren committed Apr 23, 2025 at 21:27 UTC 7b21c464890ec991d95c88b5d68802a6c8b3bd8e
10 files changed +196 -66
compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts
+2 -2
@@ -183,7 +183,7 @@ export type LoggerEvent =
183 | CompileSkipEvent
184 | PipelineErrorEvent
185 | TimingEvent
186 - | AutoDepsDecorations;
186 + | AutoDepsDecorationsEvent;
187
188 export type CompileErrorEvent = {
189 kind: 'CompileError';
@@ -220,7 +220,7 @@ export type TimingEvent = {
220 kind: 'Timing';
221 measurement: PerformanceMeasure;
222 };
223 -export type AutoDepsDecorations = {
223 +export type AutoDepsDecorationsEvent = {
224 kind: 'AutoDepsDecorations';
225 useEffectCallExpr: t.SourceLocation;
226 decorations: Array<t.SourceLocation>;
compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts
+4 -1
@@ -380,7 +380,10 @@ function collectDepUsages(
380
381 for (const [, block] of fnExpr.loweredFunc.func.body.blocks) {
382 for (const instr of block.instructions) {
383 - if (instr.value.kind === 'LoadLocal') {
383 + if (
384 + instr.value.kind === 'LoadLocal' &&
385 + identifiers.has(instr.value.place.identifier.id)
386 + ) {
387 loadedDeps.add(instr.lvalue.identifier.id);
388 }
389 for (const place of eachInstructionOperand(instr)) {
compiler/packages/react-forgive/client/src/colors.ts new
+73
@@ -0,0 +1,73 @@
1 +type RGB = [number, number, number];
2 +
3 +const int = Math.floor;
4 +
5 +export class Color {
6 + constructor(
7 + private r: number,
8 + private g: number,
9 + private b: number,
10 + ) {}
11 +
12 + toAlphaString(a: number) {
13 + return this.toCssString(a);
14 + }
15 + toString() {
16 + return this.toCssString(1);
17 + }
18 +
19 + /**
20 + * Adjust the color by a multiplier to lighten (`> 1.0`) or darken (`< 1.0`) the color. Returns a new
21 + * instance.
22 + */
23 + adjusted(mult: number) {
24 + const adjusted = Color.redistribute([
25 + this.r * mult,
26 + this.g * mult,
27 + this.b * mult,
28 + ]);
29 + return new Color(...adjusted);
30 + }
31 +
32 + private toCssString(a: number) {
33 + return `rgba(${this.r},${this.g},${this.b},${a})`;
34 + }
35 + /**
36 + * Redistributes rgb, maintaing hue until its clamped.
37 + * https://stackoverflow.com/a/141943
38 + */
39 + private static redistribute([r, g, b]: RGB): RGB {
40 + const threshold = 255.999;
41 + const max = Math.max(r, g, b);
42 + if (max <= threshold) {
43 + return [int(r), int(g), int(b)];
44 + }
45 + const total = r + g + b;
46 + if (total >= 3 * threshold) {
47 + return [int(threshold), int(threshold), int(threshold)];
48 + }
49 + const x = (3 * threshold - total) / (3 * max - total);
50 + const gray = threshold - x * max;
51 + return [int(gray + x * r), int(gray + x * g), int(gray + x * b)];
52 + }
53 +}
54 +
55 +export const BLACK = new Color(0, 0, 0);
56 +export const WHITE = new Color(255, 255, 255);
57 +
58 +const COLOR_POOL = [
59 + new Color(249, 65, 68),
60 + new Color(243, 114, 44),
61 + new Color(248, 150, 30),
62 + new Color(249, 132, 74),
63 + new Color(249, 199, 79),
64 + new Color(144, 190, 109),
65 + new Color(67, 170, 139),
66 + new Color(77, 144, 142),
67 + new Color(87, 117, 144),
68 + new Color(39, 125, 161),
69 +];
70 +
71 +export function getColorFor(index: number): Color {
72 + return COLOR_POOL[Math.abs(index) % COLOR_POOL.length]!;
73 +}
compiler/packages/react-forgive/client/src/extension.ts
+43 -17
@@ -4,12 +4,30 @@ import * as vscode from 'vscode';
4 import {
5 LanguageClient,
6 LanguageClientOptions,
7 - Position,
7 + type Position,
8 + RequestType,
9 ServerOptions,
10 TransportKind,
11 } from 'vscode-languageclient/node';
12 +import {WHITE} from './colors';
13
14 let client: LanguageClient;
15 +const inferredEffectDepDecoration =
16 + vscode.window.createTextEditorDecorationType({
17 + backgroundColor: WHITE.toAlphaString(0.3),
18 + });
19 +
20 +type Range = [Position, Position];
21 +interface AutoDepsDecorationsParams {
22 + position: Position;
23 +}
24 +namespace AutoDepsDecorationsRequest {
25 + export const type = new RequestType<
26 + AutoDepsDecorationsParams,
27 + Array<Range> | null,
28 + void
29 + >('react/autodeps_decorations');
30 +}
31
32 export function activate(context: vscode.ExtensionContext) {
33 const serverModule = context.asAbsolutePath(path.join('dist', 'server.js'));
@@ -54,23 +72,24 @@ export function activate(context: vscode.ExtensionContext) {
72 vscode.languages.registerHoverProvider(documentSelector, {
73 provideHover(_document, position, _token) {
74 client
57 - .sendRequest('react/autodepsdecorations', position)
58 - .then((decorations: Array<[Position, Position]>) => {
59 - for (const [start, end] of decorations) {
60 - const range = new vscode.Range(
61 - new vscode.Position(start.line, start.character),
62 - new vscode.Position(end.line, end.character),
75 + .sendRequest(AutoDepsDecorationsRequest.type, {position})
76 + .then(decorations => {
77 + if (Array.isArray(decorations)) {
78 + const decorationOptions = decorations.map(([start, end]) => {
79 + return {
80 + range: new vscode.Range(
81 + new vscode.Position(start.line, start.character),
82 + new vscode.Position(end.line, end.character),
83 + ),
84 + hoverMessage: 'Inferred as an effect dependency',
85 + };
86 + });
87 + vscode.window.activeTextEditor?.setDecorations(
88 + inferredEffectDepDecoration,
89 + decorationOptions,
90 );
64 - const vscodeDecoration =
65 - vscode.window.createTextEditorDecorationType({
66 - backgroundColor: 'red',
67 - });
68 - vscode.window.activeTextEditor?.setDecorations(vscodeDecoration, [
69 - {
70 - range,
71 - hoverMessage: 'hehe',
72 - },
73 - ]);
91 + } else {
92 + clearDecorations(inferredEffectDepDecoration);
93 }
94 });
95 return null;
@@ -85,4 +104,11 @@ export function deactivate(): Thenable<void> | undefined {
104 if (client !== undefined) {
105 return client.stop();
106 }
107 + return;
108 +}
109 +
110 +export function clearDecorations(
111 + decorationType: vscode.TextEditorDecorationType,
112 +) {
113 + vscode.window.activeTextEditor?.setDecorations(decorationType, []);
114 }
compiler/packages/react-forgive/server/src/custom-requests/autodepsdecorations.ts deleted
-18
@@ -1,18 +0,0 @@
1 -import {AutoDepsDecorations} from 'babel-plugin-react-compiler/src/Entrypoint';
2 -import {Position} from 'vscode-languageserver-textdocument';
3 -import {sourceLocationToRange} from '../utils/lsp-adapter';
4 -
5 -export type Range = [Position, Position];
6 -export type AutoDepsDecorationsLSPEvent = {
7 - useEffectCallExpr: Range;
8 - decorations: Array<Range>;
9 -};
10 -
11 -export function mapCompilerEventToLSPEvent(
12 - event: AutoDepsDecorations,
13 -): AutoDepsDecorationsLSPEvent {
14 - return {
15 - useEffectCallExpr: sourceLocationToRange(event.useEffectCallExpr),
16 - decorations: event.decorations.map(sourceLocationToRange),
17 - };
18 -}
compiler/packages/react-forgive/server/src/index.ts
+11 -17
@@ -5,7 +5,7 @@
5 * LICENSE file in the root directory of this source tree.
6 */
7
8 -import {Position, TextDocument} from 'vscode-languageserver-textdocument';
8 +import {TextDocument} from 'vscode-languageserver-textdocument';
9 import {
10 CodeLens,
11 createConnection,
@@ -19,15 +19,17 @@ import {compile, lastResult} from './compiler';
19 import {type PluginOptions} from 'babel-plugin-react-compiler/src';
20 import {resolveReactConfig} from './compiler/options';
21 import {
22 - CompileSuccessEvent,
22 + type CompileSuccessEvent,
23 + type LoggerEvent,
24 defaultOptions,
24 - LoggerEvent,
25 } from 'babel-plugin-react-compiler/src/Entrypoint/Options';
26 import {babelLocationToRange, getRangeFirstCharacter} from './compiler/compat';
27 import {
28 - AutoDepsDecorationsLSPEvent,
28 + type AutoDepsDecorationsLSPEvent,
29 + AutoDepsDecorationsRequest,
30 mapCompilerEventToLSPEvent,
30 -} from './custom-requests/autodepsdecorations';
31 +} from './requests/autodepsdecorations';
32 +import {isPositionWithinRange} from './utils/range';
33
34 const SUPPORTED_LANGUAGE_IDS = new Set([
35 'javascript',
@@ -155,19 +157,11 @@ connection.onCodeLensResolve(lens => {
157 return lens;
158 });
159
158 -connection.onRequest('react/autodepsdecorations', (position: Position) => {
159 - connection.console.log('Client hovering on: ' + JSON.stringify(position));
160 - connection.console.log(JSON.stringify(autoDepsDecorations, null, 2));
161 -
160 +connection.onRequest(AutoDepsDecorationsRequest.type, async params => {
161 + const position = params.position;
162 + connection.console.debug('Client hovering on: ' + JSON.stringify(position));
163 for (const dec of autoDepsDecorations) {
163 - // TODO: extract to helper
164 - if (
165 - position.line >= dec.useEffectCallExpr[0].line &&
166 - position.line <= dec.useEffectCallExpr[1].line
167 - ) {
168 - connection.console.log(
169 - 'found decoration: ' + JSON.stringify(dec.decorations),
170 - );
164 + if (isPositionWithinRange(position, dec.useEffectCallExpr)) {
165 return dec.decorations;
166 }
167 }
compiler/packages/react-forgive/server/src/requests/autodepsdecorations.ts new
+28
@@ -0,0 +1,28 @@
1 +import {type AutoDepsDecorationsEvent} from 'babel-plugin-react-compiler/src/Entrypoint';
2 +import {type Position} from 'vscode-languageserver-textdocument';
3 +import {RequestType} from 'vscode-languageserver/node';
4 +import {type Range, sourceLocationToRange} from '../utils/range';
5 +
6 +export type AutoDepsDecorationsLSPEvent = {
7 + useEffectCallExpr: Range;
8 + decorations: Array<Range>;
9 +};
10 +export interface AutoDepsDecorationsParams {
11 + position: Position;
12 +}
13 +export namespace AutoDepsDecorationsRequest {
14 + export const type = new RequestType<
15 + AutoDepsDecorationsParams,
16 + Array<Range> | null,
17 + void
18 + >('react/autodeps_decorations');
19 +}
20 +
21 +export function mapCompilerEventToLSPEvent(
22 + event: AutoDepsDecorationsEvent,
23 +): AutoDepsDecorationsLSPEvent {
24 + return {
25 + useEffectCallExpr: sourceLocationToRange(event.useEffectCallExpr),
26 + decorations: event.decorations.map(sourceLocationToRange),
27 + };
28 +}
compiler/packages/react-forgive/server/src/utils/lsp-adapter.ts deleted
-11
@@ -1,11 +0,0 @@
1 -import * as t from '@babel/types';
2 -import {Position} from 'vscode-languageserver/node';
3 -
4 -export function sourceLocationToRange(
5 - loc: t.SourceLocation,
6 -): [Position, Position] {
7 - return [
8 - {line: loc.start.line - 1, character: loc.start.column},
9 - {line: loc.end.line - 1, character: loc.end.column},
10 - ];
11 -}
compiler/packages/react-forgive/server/src/utils/range.ts new
+19
@@ -0,0 +1,19 @@
1 +import * as t from '@babel/types';
2 +import {type Position} from 'vscode-languageserver/node';
3 +
4 +export type Range = [Position, Position];
5 +export function isPositionWithinRange(
6 + position: Position,
7 + [start, end]: Range,
8 +): boolean {
9 + return position.line >= start.line && position.line <= end.line;
10 +}
11 +
12 +export function sourceLocationToRange(
13 + loc: t.SourceLocation,
14 +): [Position, Position] {
15 + return [
16 + {line: loc.start.line - 1, character: loc.start.column},
17 + {line: loc.end.line - 1, character: loc.end.column},
18 + ];
19 +}
compiler/packages/react-forgive/tsconfig.json new
+16
@@ -0,0 +1,16 @@
1 +{
2 + "extends": "@tsconfig/strictest/tsconfig.json",
3 + "compilerOptions": {
4 + "module": "Node16",
5 + "moduleResolution": "Node16",
6 + "rootDir": "../",
7 + "noEmit": true,
8 + "jsx": "react-jsxdev",
9 + "lib": ["ES2022"],
10 +
11 + "target": "ES2022",
12 + "importsNotUsedAsValues": "remove",
13 + },
14 + "exclude": ["node_modules"],
15 + "include": ["server/src/**/*.ts", "client/src/**/*.ts"],
16 +}