Skip to content

Commit f765082

Browse files
potetojbrown215
andauthored
[forgive] Add code action to remove dependency array (#33000)
Adds a new codeaction event in the compiler and handler in forgive. This allows you to remove a dependency array when you're editing a range that is within an autodep eligible function. Co-authored-by: Jordan Brown <[email protected]> --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33000). * #33002 * #33001 * __->__ #33000 Co-authored-by: Jordan Brown <[email protected]>
1 parent 7b21c46 commit f765082

File tree

5 files changed

+125
-9
lines changed

5 files changed

+125
-9
lines changed

compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,8 @@ export type LoggerEvent =
183183
| CompileSkipEvent
184184
| PipelineErrorEvent
185185
| TimingEvent
186-
| AutoDepsDecorationsEvent;
186+
| AutoDepsDecorationsEvent
187+
| AutoDepsEligibleEvent;
187188

188189
export type CompileErrorEvent = {
189190
kind: 'CompileError';
@@ -222,9 +223,14 @@ export type TimingEvent = {
222223
};
223224
export type AutoDepsDecorationsEvent = {
224225
kind: 'AutoDepsDecorations';
225-
useEffectCallExpr: t.SourceLocation;
226+
fnLoc: t.SourceLocation;
226227
decorations: Array<t.SourceLocation>;
227228
};
229+
export type AutoDepsEligibleEvent = {
230+
kind: 'AutoDepsEligible';
231+
fnLoc: t.SourceLocation;
232+
depArrayLoc: t.SourceLocation;
233+
};
228234

229235
export type Logger = {
230236
logEvent: (filename: string | null, event: LoggerEvent) => void;

compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts

+25-1
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ export function inferEffectDependencies(fn: HIRFunction): void {
230230
if (typeof value.loc !== 'symbol') {
231231
fn.env.logger?.logEvent(fn.env.filename, {
232232
kind: 'AutoDepsDecorations',
233-
useEffectCallExpr: value.loc,
233+
fnLoc: value.loc,
234234
decorations,
235235
});
236236
}
@@ -258,6 +258,30 @@ export function inferEffectDependencies(fn: HIRFunction): void {
258258
rewriteInstrs.set(instr.id, newInstructions);
259259
fn.env.inferredEffectLocations.add(callee.loc);
260260
}
261+
} else if (
262+
value.args.length >= 2 &&
263+
value.args.length - 1 === autodepFnLoads.get(callee.identifier.id) &&
264+
value.args[0].kind === 'Identifier'
265+
) {
266+
const penultimateArg = value.args[value.args.length - 2];
267+
const depArrayArg = value.args[value.args.length - 1];
268+
if (
269+
depArrayArg.kind !== 'Spread' &&
270+
penultimateArg.kind !== 'Spread' &&
271+
typeof depArrayArg.loc !== 'symbol' &&
272+
typeof penultimateArg.loc !== 'symbol' &&
273+
typeof value.loc !== 'symbol'
274+
) {
275+
fn.env.logger?.logEvent(fn.env.filename, {
276+
kind: 'AutoDepsEligible',
277+
fnLoc: value.loc,
278+
depArrayLoc: {
279+
...depArrayArg.loc,
280+
start: penultimateArg.loc.end,
281+
end: depArrayArg.loc.end,
282+
},
283+
});
284+
}
261285
}
262286
}
263287
}

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

+75-5
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@
77

88
import {TextDocument} from 'vscode-languageserver-textdocument';
99
import {
10+
CodeAction,
11+
CodeActionKind,
1012
CodeLens,
1113
createConnection,
1214
type InitializeParams,
1315
type InitializeResult,
16+
Position,
1417
ProposedFeatures,
1518
TextDocuments,
1619
TextDocumentSyncKind,
@@ -29,7 +32,12 @@ import {
2932
AutoDepsDecorationsRequest,
3033
mapCompilerEventToLSPEvent,
3134
} from './requests/autodepsdecorations';
32-
import {isPositionWithinRange} from './utils/range';
35+
import {
36+
isPositionWithinRange,
37+
isRangeWithinRange,
38+
Range,
39+
sourceLocationToRange,
40+
} from './utils/range';
3341

3442
const SUPPORTED_LANGUAGE_IDS = new Set([
3543
'javascript',
@@ -44,6 +52,15 @@ const documents = new TextDocuments(TextDocument);
4452
let compilerOptions: PluginOptions | null = null;
4553
let compiledFns: Set<CompileSuccessEvent> = new Set();
4654
let autoDepsDecorations: Array<AutoDepsDecorationsLSPEvent> = [];
55+
let codeActionEvents: Array<CodeActionLSPEvent> = [];
56+
57+
type CodeActionLSPEvent = {
58+
title: string;
59+
kind: CodeActionKind;
60+
newText: string;
61+
anchorRange: Range;
62+
editRange: {start: Position; end: Position};
63+
};
4764

4865
connection.onInitialize((_params: InitializeParams) => {
4966
// TODO(@poteto) get config fr
@@ -85,13 +102,24 @@ connection.onInitialize((_params: InitializeParams) => {
85102
if (event.kind === 'AutoDepsDecorations') {
86103
autoDepsDecorations.push(mapCompilerEventToLSPEvent(event));
87104
}
105+
if (event.kind === 'AutoDepsEligible') {
106+
const depArrayLoc = sourceLocationToRange(event.depArrayLoc);
107+
codeActionEvents.push({
108+
title: 'Use React Compiler inferred dependency array',
109+
kind: CodeActionKind.QuickFix,
110+
newText: '',
111+
anchorRange: sourceLocationToRange(event.fnLoc),
112+
editRange: {start: depArrayLoc[0], end: depArrayLoc[1]},
113+
});
114+
}
88115
},
89116
},
90117
};
91118
const result: InitializeResult = {
92119
capabilities: {
93120
textDocumentSync: TextDocumentSyncKind.Full,
94121
codeLensProvider: {resolveProvider: true},
122+
codeActionProvider: {resolveProvider: true},
95123
},
96124
};
97125
return result;
@@ -103,8 +131,7 @@ connection.onInitialized(() => {
103131

104132
documents.onDidChangeContent(async event => {
105133
connection.console.info(`Changed: ${event.document.uri}`);
106-
compiledFns.clear();
107-
autoDepsDecorations = [];
134+
resetState();
108135
if (SUPPORTED_LANGUAGE_IDS.has(event.document.languageId)) {
109136
const text = event.document.getText();
110137
await compile({
@@ -116,8 +143,7 @@ documents.onDidChangeContent(async event => {
116143
});
117144

118145
connection.onDidChangeWatchedFiles(change => {
119-
compiledFns.clear();
120-
autoDepsDecorations = [];
146+
resetState();
121147
connection.console.log(
122148
change.changes.map(c => `File changed: ${c.uri}`).join('\n'),
123149
);
@@ -157,6 +183,44 @@ connection.onCodeLensResolve(lens => {
157183
return lens;
158184
});
159185

186+
connection.onCodeAction(params => {
187+
connection.console.log('onCodeAction');
188+
connection.console.log(JSON.stringify(params, null, 2));
189+
const codeActions: Array<CodeAction> = [];
190+
for (const codeActionEvent of codeActionEvents) {
191+
if (
192+
isRangeWithinRange(
193+
[params.range.start, params.range.end],
194+
codeActionEvent.anchorRange,
195+
)
196+
) {
197+
codeActions.push(
198+
CodeAction.create(
199+
codeActionEvent.title,
200+
{
201+
changes: {
202+
[params.textDocument.uri]: [
203+
{
204+
newText: codeActionEvent.newText,
205+
range: codeActionEvent.editRange,
206+
},
207+
],
208+
},
209+
},
210+
codeActionEvent.kind,
211+
),
212+
);
213+
}
214+
}
215+
return codeActions;
216+
});
217+
218+
connection.onCodeActionResolve(codeAction => {
219+
connection.console.log('onCodeActionResolve');
220+
connection.console.log(JSON.stringify(codeAction, null, 2));
221+
return codeAction;
222+
});
223+
160224
connection.onRequest(AutoDepsDecorationsRequest.type, async params => {
161225
const position = params.position;
162226
connection.console.debug('Client hovering on: ' + JSON.stringify(position));
@@ -168,6 +232,12 @@ connection.onRequest(AutoDepsDecorationsRequest.type, async params => {
168232
return null;
169233
});
170234

235+
function resetState() {
236+
compiledFns.clear();
237+
autoDepsDecorations = [];
238+
codeActionEvents = [];
239+
}
240+
171241
documents.listen(connection);
172242
connection.listen();
173243
connection.console.info(`React Analyzer running in node ${process.version}`);

compiler/packages/react-forgive/server/src/requests/autodepsdecorations.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export function mapCompilerEventToLSPEvent(
2222
event: AutoDepsDecorationsEvent,
2323
): AutoDepsDecorationsLSPEvent {
2424
return {
25-
useEffectCallExpr: sourceLocationToRange(event.useEffectCallExpr),
25+
useEffectCallExpr: sourceLocationToRange(event.fnLoc),
2626
decorations: event.decorations.map(sourceLocationToRange),
2727
};
2828
}

compiler/packages/react-forgive/server/src/utils/range.ts

+16
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,29 @@ import * as t from '@babel/types';
22
import {type Position} from 'vscode-languageserver/node';
33

44
export type Range = [Position, Position];
5+
56
export function isPositionWithinRange(
67
position: Position,
78
[start, end]: Range,
89
): boolean {
910
return position.line >= start.line && position.line <= end.line;
1011
}
1112

13+
export function isRangeWithinRange(aRange: Range, bRange: Range): boolean {
14+
const startComparison = comparePositions(aRange[0], bRange[0]);
15+
const endComparison = comparePositions(aRange[1], bRange[1]);
16+
return startComparison >= 0 && endComparison <= 0;
17+
}
18+
19+
function comparePositions(a: Position, b: Position): number {
20+
const lineComparison = a.line - b.line;
21+
if (lineComparison === 0) {
22+
return a.character - b.character;
23+
} else {
24+
return lineComparison;
25+
}
26+
}
27+
1228
export function sourceLocationToRange(
1329
loc: t.SourceLocation,
1430
): [Position, Position] {

0 commit comments

Comments
 (0)