diff --git a/ivette/.eslintrc.js b/ivette/.eslintrc.js index 32bd12c19a23afe635ae2dc5b4edc8f88b196c85..00bfecaef72a80a2857f7d7042c24c42e7baa8a9 100644 --- a/ivette/.eslintrc.js +++ b/ivette/.eslintrc.js @@ -17,6 +17,8 @@ module.exports = { }, rules: { "react/display-name": "off", + // Be more strict on usage of useMemo and useRef + "react-hooks/exhaustive-deps": "error", // Allow type any, even if it should be avoided "@typescript-eslint/no-explicit-any": "off", // Allow functions without return type, even if exported function should have one diff --git a/ivette/src/frama-c/states.ts b/ivette/src/frama-c/states.ts index c00809c195bba44f12fa413fdeb2cffba8ac44b3..f822f19f1e3596b9fe42f524db6e5c59ba766527 100644 --- a/ivette/src/frama-c/states.ts +++ b/ivette/src/frama-c/states.ts @@ -158,40 +158,42 @@ export function useState(id: string) { * in case of errors, but will keep the last received value until a new one is * actually received. */ -export function useRequest(rq: string, params: any, options: any = {}) { +export function useRequest( + rq: string, + params: any, + options: any = {}, +) { + const state = React.useRef<string>(); const project = useProject(); - const [value, setValue] = React.useState(options.offline); + const [response, setResponse] = React.useState(options.offline); + const footprint = + project ? JSON.stringify([project, rq, params]) : undefined; - React.useEffect(() => { + async function trigger() { if (project) { - const pending = options.prending; - if (pending !== null) { - setValue(pending); + try { + const r = await Server.GET({ endpoint: rq, params }); + setResponse(r); + } catch (errmsg) { + if (Dome.DEVEL) + console.warn(`[Server] use request '${rq}':`, errmsg); + const err = options.error; + if (err !== undefined) setResponse(err); } - (async () => { - try { - const sr: Server.Request = { endpoint: rq, params }; - const v = await Server.GET(sr); - setValue(v); - } catch (err) { - if (Dome.DEVEL) { - console.warn(`[Server] use request '${rq}':`, err); - } - const { error } = options; - if (error !== null) { - setValue(error); - } - } - })(); } else { - const v = options.offline; - if (value !== v) { - setValue(v); - } + const off = options.offline; + if (off !== undefined) setResponse(off); } - }, [project, rq, JSON.stringify(params)]); + } + + React.useEffect(() => { + if (state.current !== footprint) { + state.current = footprint; + trigger(); + } + }); - return value; + return response; } // -------------------------------------------------------------------------- @@ -243,7 +245,7 @@ export function useDictionary( if (k && (!filter || filter(tg))) d[k] = tg; }); return d; - }, [tags, filter]); + }, [key, tags, filter]); return dict; } diff --git a/ivette/src/renderer/ASTview.tsx b/ivette/src/renderer/ASTview.tsx index d7b941609926144dd3ba8cf0cce9045310c54d23..78c071618b827b2b70fe1f4b9d7cb3b51ec9be76 100644 --- a/ivette/src/renderer/ASTview.tsx +++ b/ivette/src/renderer/ASTview.tsx @@ -18,13 +18,13 @@ import 'codemirror/theme/ambiance.css'; // --- Rich Text Printer // -------------------------------------------------------------------------- -const print = (buffer: any, text: string) => { +const printAST = (buffer: any, text: string) => { if (Array.isArray(text)) { const tag = text.shift(); if (tag !== '') { buffer.openTextMarker({ id: tag }); } - text.forEach((txt) => print(buffer, txt)); + text.forEach((txt) => printAST(buffer, txt)); if (tag !== '') { buffer.closeTextMarker(); } @@ -33,45 +33,51 @@ const print = (buffer: any, text: string) => { } }; +async function loadAST(buffer: any, theFunction?: string, theMarker?: string) { + buffer.clear(); + if (theFunction) { + buffer.log('// Loading', theFunction, '…'); + (async () => { + const data = await Server.GET({ + endpoint: 'kernel.ast.printFunction', + params: theFunction, + }); + buffer.clear(); + if (!data) + buffer.log('// No code for function ', theFunction); + printAST(buffer, data); + if (theMarker) + buffer.scroll(theMarker, undefined); + return; + })(); + } +} + // -------------------------------------------------------------------------- // --- AST Printer // -------------------------------------------------------------------------- const ASTview = () => { + // Hooks const buffer = React.useMemo(() => new RichTextBuffer(), []); + const printed = React.useRef(); const [select, setSelect] = States.useSelection(); const theFunction = select && select.function; const theMarker = select && select.marker; // Hook: async loading React.useEffect(() => { - buffer.clear(); - if (theFunction) { - buffer.log('// Loading', theFunction, '…'); - (async () => { - const sr: Server.Request = { - endpoint: 'kernel.ast.printFunction', - params: theFunction, - }; - const data = await Server.GET(sr); - buffer.clear(); - if (!data) { - buffer.log('// No code for function ', theFunction); - } - print(buffer, data); - if (theMarker) { - buffer.scroll(theMarker, undefined); - } - return; - })(); + if (printed.current !== theFunction) { + printed.current = theFunction; + loadAST(buffer, theFunction, theMarker); } - }, [theFunction]); + }); - // Hook: scrolling + // Hook: marker scrolling React.useEffect(() => { if (theMarker) buffer.scroll(theMarker, undefined); - }, [theMarker]); + }, [buffer, theMarker]); // Callbacks const onSelection = (marker: any) => setSelect({ marker }); @@ -89,6 +95,7 @@ const ASTview = () => { /> </Vfill> ); + }; // --------------------------------------------------------------------------