mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[forgive] Add code action to remove dependency array
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 <jmbrown@meta.com>
This commit is contained in:
co-authored by
Jordan Brown
parent
d6a957940c
commit
8f91c97d1c
@@ -183,7 +183,8 @@ export type LoggerEvent =
|
||||
| CompileSkipEvent
|
||||
| PipelineErrorEvent
|
||||
| TimingEvent
|
||||
| AutoDepsDecorationsEvent;
|
||||
| AutoDepsDecorationsEvent
|
||||
| AutoDepsEligibleEvent;
|
||||
|
||||
export type CompileErrorEvent = {
|
||||
kind: 'CompileError';
|
||||
@@ -222,9 +223,14 @@ export type TimingEvent = {
|
||||
};
|
||||
export type AutoDepsDecorationsEvent = {
|
||||
kind: 'AutoDepsDecorations';
|
||||
useEffectCallExpr: t.SourceLocation;
|
||||
fnLoc: t.SourceLocation;
|
||||
decorations: Array<t.SourceLocation>;
|
||||
};
|
||||
export type AutoDepsEligibleEvent = {
|
||||
kind: 'AutoDepsEligible';
|
||||
fnLoc: t.SourceLocation;
|
||||
depArrayLoc: t.SourceLocation;
|
||||
};
|
||||
|
||||
export type Logger = {
|
||||
logEvent: (filename: string | null, event: LoggerEvent) => void;
|
||||
|
||||
+25
-1
@@ -216,7 +216,7 @@ export function inferEffectDependencies(fn: HIRFunction): void {
|
||||
if (typeof value.loc !== 'symbol') {
|
||||
fn.env.logger?.logEvent(fn.env.filename, {
|
||||
kind: 'AutoDepsDecorations',
|
||||
useEffectCallExpr: value.loc,
|
||||
fnLoc: value.loc,
|
||||
decorations: collectDepUsages(usedDeps, fnExpr.value)
|
||||
.map(loc => (typeof loc !== 'symbol' ? loc : null))
|
||||
.filter(loc => loc !== null),
|
||||
@@ -246,6 +246,30 @@ export function inferEffectDependencies(fn: HIRFunction): void {
|
||||
rewriteInstrs.set(instr.id, newInstructions);
|
||||
fn.env.inferredEffectLocations.add(callee.loc);
|
||||
}
|
||||
} else if (
|
||||
value.args.length >= 2 &&
|
||||
value.args.length - 1 === autodepFnLoads.get(callee.identifier.id) &&
|
||||
value.args[0].kind === 'Identifier'
|
||||
) {
|
||||
const penultimateArg = value.args[value.args.length - 2];
|
||||
const depArrayArg = value.args[value.args.length - 1];
|
||||
if (
|
||||
depArrayArg.kind !== 'Spread' &&
|
||||
penultimateArg.kind !== 'Spread' &&
|
||||
typeof depArrayArg.loc !== 'symbol' &&
|
||||
typeof penultimateArg.loc !== 'symbol' &&
|
||||
typeof value.loc !== 'symbol'
|
||||
) {
|
||||
fn.env.logger?.logEvent(fn.env.filename, {
|
||||
kind: 'AutoDepsEligible',
|
||||
fnLoc: value.loc,
|
||||
depArrayLoc: {
|
||||
...depArrayArg.loc,
|
||||
start: penultimateArg.loc.end,
|
||||
end: depArrayArg.loc.end,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,13 @@
|
||||
|
||||
import {TextDocument} from 'vscode-languageserver-textdocument';
|
||||
import {
|
||||
CodeAction,
|
||||
CodeActionKind,
|
||||
CodeLens,
|
||||
createConnection,
|
||||
type InitializeParams,
|
||||
type InitializeResult,
|
||||
Position,
|
||||
ProposedFeatures,
|
||||
TextDocuments,
|
||||
TextDocumentSyncKind,
|
||||
@@ -29,7 +32,12 @@ import {
|
||||
AutoDepsDecorationsRequest,
|
||||
mapCompilerEventToLSPEvent,
|
||||
} from './requests/autodepsdecorations';
|
||||
import {isPositionWithinRange} from './utils/range';
|
||||
import {
|
||||
isPositionWithinRange,
|
||||
isRangeWithinRange,
|
||||
Range,
|
||||
sourceLocationToRange,
|
||||
} from './utils/range';
|
||||
|
||||
const SUPPORTED_LANGUAGE_IDS = new Set([
|
||||
'javascript',
|
||||
@@ -44,6 +52,15 @@ const documents = new TextDocuments(TextDocument);
|
||||
let compilerOptions: PluginOptions | null = null;
|
||||
let compiledFns: Set<CompileSuccessEvent> = new Set();
|
||||
let autoDepsDecorations: Array<AutoDepsDecorationsLSPEvent> = [];
|
||||
let codeActionEvents: Array<CodeActionLSPEvent> = [];
|
||||
|
||||
type CodeActionLSPEvent = {
|
||||
title: string;
|
||||
kind: CodeActionKind;
|
||||
newText: string;
|
||||
anchorRange: Range;
|
||||
editRange: {start: Position; end: Position};
|
||||
};
|
||||
|
||||
connection.onInitialize((_params: InitializeParams) => {
|
||||
// TODO(@poteto) get config fr
|
||||
@@ -85,6 +102,16 @@ connection.onInitialize((_params: InitializeParams) => {
|
||||
if (event.kind === 'AutoDepsDecorations') {
|
||||
autoDepsDecorations.push(mapCompilerEventToLSPEvent(event));
|
||||
}
|
||||
if (event.kind === 'AutoDepsEligible') {
|
||||
const depArrayLoc = sourceLocationToRange(event.depArrayLoc);
|
||||
codeActionEvents.push({
|
||||
title: 'Use React Compiler inferred dependency array',
|
||||
kind: CodeActionKind.QuickFix,
|
||||
newText: '',
|
||||
anchorRange: sourceLocationToRange(event.fnLoc),
|
||||
editRange: {start: depArrayLoc[0], end: depArrayLoc[1]},
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -92,6 +119,7 @@ connection.onInitialize((_params: InitializeParams) => {
|
||||
capabilities: {
|
||||
textDocumentSync: TextDocumentSyncKind.Full,
|
||||
codeLensProvider: {resolveProvider: true},
|
||||
codeActionProvider: {resolveProvider: true},
|
||||
},
|
||||
};
|
||||
return result;
|
||||
@@ -103,8 +131,7 @@ connection.onInitialized(() => {
|
||||
|
||||
documents.onDidChangeContent(async event => {
|
||||
connection.console.info(`Changed: ${event.document.uri}`);
|
||||
compiledFns.clear();
|
||||
autoDepsDecorations = [];
|
||||
resetState();
|
||||
if (SUPPORTED_LANGUAGE_IDS.has(event.document.languageId)) {
|
||||
const text = event.document.getText();
|
||||
await compile({
|
||||
@@ -116,8 +143,7 @@ documents.onDidChangeContent(async event => {
|
||||
});
|
||||
|
||||
connection.onDidChangeWatchedFiles(change => {
|
||||
compiledFns.clear();
|
||||
autoDepsDecorations = [];
|
||||
resetState();
|
||||
connection.console.log(
|
||||
change.changes.map(c => `File changed: ${c.uri}`).join('\n'),
|
||||
);
|
||||
@@ -157,6 +183,44 @@ connection.onCodeLensResolve(lens => {
|
||||
return lens;
|
||||
});
|
||||
|
||||
connection.onCodeAction(params => {
|
||||
connection.console.log('onCodeAction');
|
||||
connection.console.log(JSON.stringify(params, null, 2));
|
||||
const codeActions: Array<CodeAction> = [];
|
||||
for (const codeActionEvent of codeActionEvents) {
|
||||
if (
|
||||
isRangeWithinRange(
|
||||
[params.range.start, params.range.end],
|
||||
codeActionEvent.anchorRange,
|
||||
)
|
||||
) {
|
||||
codeActions.push(
|
||||
CodeAction.create(
|
||||
codeActionEvent.title,
|
||||
{
|
||||
changes: {
|
||||
[params.textDocument.uri]: [
|
||||
{
|
||||
newText: codeActionEvent.newText,
|
||||
range: codeActionEvent.editRange,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
codeActionEvent.kind,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
return codeActions;
|
||||
});
|
||||
|
||||
connection.onCodeActionResolve(codeAction => {
|
||||
connection.console.log('onCodeActionResolve');
|
||||
connection.console.log(JSON.stringify(codeAction, null, 2));
|
||||
return codeAction;
|
||||
});
|
||||
|
||||
connection.onRequest(AutoDepsDecorationsRequest.type, async params => {
|
||||
const position = params.position;
|
||||
connection.console.debug('Client hovering on: ' + JSON.stringify(position));
|
||||
@@ -168,6 +232,12 @@ connection.onRequest(AutoDepsDecorationsRequest.type, async params => {
|
||||
return null;
|
||||
});
|
||||
|
||||
function resetState() {
|
||||
compiledFns.clear();
|
||||
autoDepsDecorations = [];
|
||||
codeActionEvents = [];
|
||||
}
|
||||
|
||||
documents.listen(connection);
|
||||
connection.listen();
|
||||
connection.console.info(`React Analyzer running in node ${process.version}`);
|
||||
|
||||
@@ -22,7 +22,7 @@ export function mapCompilerEventToLSPEvent(
|
||||
event: AutoDepsDecorationsEvent,
|
||||
): AutoDepsDecorationsLSPEvent {
|
||||
return {
|
||||
useEffectCallExpr: sourceLocationToRange(event.useEffectCallExpr),
|
||||
useEffectCallExpr: sourceLocationToRange(event.fnLoc),
|
||||
decorations: event.decorations.map(sourceLocationToRange),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ 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,
|
||||
@@ -9,6 +10,21 @@ export function isPositionWithinRange(
|
||||
return position.line >= start.line && position.line <= end.line;
|
||||
}
|
||||
|
||||
export function isRangeWithinRange(aRange: Range, bRange: Range): boolean {
|
||||
const startComparison = comparePositions(aRange[0], bRange[0]);
|
||||
const endComparison = comparePositions(aRange[1], bRange[1]);
|
||||
return startComparison >= 0 && endComparison <= 0;
|
||||
}
|
||||
|
||||
function comparePositions(a: Position, b: Position): number {
|
||||
const lineComparison = a.line - b.line;
|
||||
if (lineComparison === 0) {
|
||||
return a.character - b.character;
|
||||
} else {
|
||||
return lineComparison;
|
||||
}
|
||||
}
|
||||
|
||||
export function sourceLocationToRange(
|
||||
loc: t.SourceLocation,
|
||||
): [Position, Position] {
|
||||
|
||||
Reference in New Issue
Block a user