diff --git a/ivette/src/frama-c/kernel/ASTview.tsx b/ivette/src/frama-c/kernel/ASTview.tsx index 339961b65b0344a4dddf128fdbbeb1b71abb56b5..69f38d5ca7f5c5b0da517a438fd120f20d75b90b 100644 --- a/ivette/src/frama-c/kernel/ASTview.tsx +++ b/ivette/src/frama-c/kernel/ASTview.tsx @@ -20,8 +20,6 @@ /* */ /* ************************************************************************ */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ - // -------------------------------------------------------------------------- // --- AST Source Code // -------------------------------------------------------------------------- @@ -33,10 +31,16 @@ import * as States from 'frama-c/states'; import * as RichText from 'frama-c/richtext'; import * as Dome from 'dome'; +import type { key } from 'dome/data/json'; import { RichTextBuffer } from 'dome/text/buffers'; import { Text } from 'dome/text/editors'; + +import { TitleBar } from 'ivette'; import * as Preferences from 'ivette/prefs'; -import * as Settings from 'dome/data/settings'; + +import * as Server from 'frama-c/server'; +import * as States from 'frama-c/states'; +import * as Utils from 'frama-c/utils'; import * as Ast from 'frama-c/kernel/api/ast'; import * as Properties from 'frama-c/kernel/api/properties'; @@ -55,7 +59,7 @@ const D = new Dome.Debug('AST View'); async function loadAST( buffer: RichTextBuffer, theFunction?: string, theMarker?: string, -) { +): Promise<void> { buffer.clear(); if (theFunction) { buffer.log('// Loading', theFunction, '…'); @@ -83,7 +87,9 @@ async function loadAST( /* --- Function Callers ---*/ /* --------------------------------------------------------------------------*/ -async function functionCallers(functionName: string) { +type Caller = { fct: key<'#fct'>, marker: key<'#stmt'> }; + +async function functionCallers(functionName: string): Promise<Caller[]> { try { const data = await Server.send(getCallers, functionName); const locations = data.map(([fct, marker]) => ({ fct, marker })); @@ -99,12 +105,18 @@ async function functionCallers(functionName: string) { /* --------------------------------------------------------------------------*/ type access = 'Reads' | 'Writes'; +interface StudiaInfos { + name: string, + title: string, + locations: { fct: key<'#fct'>, marker: Ast.marker }[], + index: number, +} async function studia( marker: string, info: Ast.markerInfoData, kind: access, -) { +): Promise<StudiaInfos> { const request = kind === 'Reads' ? getReadsLval : getWritesLval; const data = await Server.send(request, marker); const locations = data.direct.map(([f, m]) => ({ fct: f, marker: m })); @@ -124,7 +136,7 @@ async function studia( /* --- Property Bullets ---*/ /* --------------------------------------------------------------------------*/ -function getBulletColor(status: States.Tag) { +function getBulletColor(status: States.Tag): string { switch (status.name) { case 'unknown': return '#FF8300'; case 'invalid': @@ -141,7 +153,7 @@ function getBulletColor(status: States.Tag) { } } -function makeBullet(status: States.Tag) { +function makeBullet(status: States.Tag): HTMLDivElement { const marker = document.createElement('div'); marker.style.color = getBulletColor(status); if (status.descr) @@ -155,7 +167,7 @@ function makeBullet(status: States.Tag) { // --- AST Printer // -------------------------------------------------------------------------- -export default function ASTview() { +export default function ASTview(): JSX.Element { // Hooks const buffer = React.useMemo(() => new RichTextBuffer(), []); @@ -200,7 +212,7 @@ export default function ASTview() { return () => { buffer.off('change', setBullets); }; }, [buffer, setBullets]); - async function reload() { + async function reload(): Promise<void> { printed.current = theFunction; loadAST(buffer, theFunction, theMarker); } @@ -215,7 +227,7 @@ export default function ASTview() { Server.onSignal(Ast.changed, reload); React.useEffect(() => { - const decorator = (marker: string) => { + const decorator = (marker: string): string | undefined => { if (multipleSelections?.some((location) => location?.marker === marker)) return 'highlighted-marker'; if (deadCode?.unreachable?.some((m) => m === marker)) @@ -247,7 +259,7 @@ export default function ASTview() { if (meta) States.MetaSelection.emit(location); } - async function onContextMenu(markerId: string) { + async function onContextMenu(markerId: string): Promise<void> { const items = []; const selectedMarkerInfo = markersInfo.getData(markerId); if (selectedMarkerInfo?.var === 'function') { @@ -283,7 +295,7 @@ export default function ASTview() { } const enabled = selectedMarkerInfo?.kind === 'lvalue' || selectedMarkerInfo?.var === 'variable'; - function onClick(kind: access) { + function onClick(kind: access): void { if (selectedMarkerInfo) studia( markerId, diff --git a/ivette/src/frama-c/kernel/Messages.tsx b/ivette/src/frama-c/kernel/Messages.tsx index c570560a0564778a4396fcc4a538a980369c919a..93c61b8a7a773bee950b267ce900f3ab5cc8adea 100644 --- a/ivette/src/frama-c/kernel/Messages.tsx +++ b/ivette/src/frama-c/kernel/Messages.tsx @@ -20,11 +20,8 @@ /* */ /* ************************************************************************ */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ - import * as React from 'react'; import * as Dome from 'dome'; -import { TitleBar } from 'ivette'; import { IconButton } from 'dome/controls/buttons'; import { Label, Cell } from 'dome/controls/labels'; @@ -36,8 +33,9 @@ import * as Forms from 'dome/layout/forms'; import * as Arrays from 'dome/table/arrays'; import { Table, Column, Renderer } from 'dome/table/views'; import * as Compare from 'dome/data/compare'; - import { State, GlobalState, useGlobalState } from 'dome/data/states'; + +import { TitleBar } from 'ivette'; import * as States from 'frama-c/states'; import * as Ast from 'frama-c/kernel/api/ast'; import * as Kernel from 'frama-c/kernel/api/services'; @@ -114,11 +112,11 @@ const defaultFilter: Filter = { emitter: emitterFilter, }; -function filterKind(filter: KindFilter, msg: Message) { +function filterKind(filter: KindFilter, msg: Message): boolean { return filter[msg.kind]; } -function filterEmitter(filter: EmitterFilter, msg: Message) { +function filterEmitter(filter: EmitterFilter, msg: Message): boolean { if (msg.plugin === 'kernel') return filter.kernel; if (msg.plugin in filter.plugins) @@ -126,7 +124,10 @@ function filterEmitter(filter: EmitterFilter, msg: Message) { return filter.others; } -function searchCategory(search: string | undefined, msg: string | undefined) { +function searchCategory( + search: string | undefined, + msg: string | undefined +): boolean { if (!search || search.length < 2) return true; if (!msg) @@ -151,7 +152,7 @@ function searchCategory(search: string | undefined, msg: string | undefined) { return (empty || show) && !hide; } -function searchString(search: string | undefined, msg: string) { +function searchString(search: string | undefined, msg: string): boolean { if (!search || search.length < 3) return true; if (search.charAt(0) === '"' && search.slice(-1) === '"') { @@ -168,18 +169,26 @@ function searchString(search: string | undefined, msg: string) { return show; } -function filterSearched(search: Search, msg: Message) { +function filterSearched(search: Search, msg: Message): boolean { return (searchString(search.message, msg.message) && searchCategory(search.category, msg.category)); } -function filterFunction(filter: Filter, kf: string | undefined, msg: Message) { +function filterFunction( + filter: Filter, + kf: string | undefined, + msg: Message +): boolean { if (filter.currentFct) return (kf === msg.fct); return true; } -function filterMessage(filter: Filter, kf: string | undefined, msg: Message) { +function filterMessage( + filter: Filter, + kf: string | undefined, + msg: Message +): boolean { return (filterFunction(filter, kf, msg) && filterSearched(filter.search, msg) && filterKind(filter.kind, msg) && @@ -190,7 +199,7 @@ function filterMessage(filter: Filter, kf: string | undefined, msg: Message) { // --- Filters panel and ratio // -------------------------------------------------------------------------- -function Section(p: Forms.SectionProps) { +function Section(p: Forms.SectionProps): JSX.Element { const settings = `ivette.messages.filter.${p.label}`; return ( <Forms.Section label={p.label} unfold settings={settings}> @@ -199,7 +208,7 @@ function Section(p: Forms.SectionProps) { ); } -function Checkbox(p: Forms.CheckboxFieldProps) { +function Checkbox(p: Forms.CheckboxFieldProps): JSX.Element { const lbl = p.label.charAt(0).toUpperCase() + p.label.slice(1).toLowerCase(); return <Forms.CheckboxField label={lbl} state={p.state} />; } @@ -207,7 +216,7 @@ function Checkbox(p: Forms.CheckboxFieldProps) { function MessageKindCheckbox(props: { kind: logkind, kindState: Forms.FieldState<KindFilter>, -}) { +}): JSX.Element { const { kind, kindState } = props; const state = Forms.useProperty(kindState, kind); return <Checkbox label={kind} state={state} />; @@ -216,12 +225,12 @@ function MessageKindCheckbox(props: { function PluginCheckbox(props: { plugin: string, pluginState: Forms.FieldState<PluginFilter>, -}) { +}): JSX.Element { const state = Forms.useProperty(props.pluginState, props.plugin); return <Checkbox label={props.plugin} state={state} />; } -function MessageFilter(props: { filter: State<Filter> }) { +function MessageFilter(props: { filter: State<Filter> }): JSX.Element { const state = Forms.useValid(props.filter); const search = Forms.useProperty(state, 'search'); const categoryState = Forms.useProperty(search, 'category'); @@ -282,7 +291,9 @@ function MessageFilter(props: { filter: State<Filter> }) { ); } -function FilterRatio<K, R>({ model }: { model: Arrays.ArrayModel<K, R> }) { +function FilterRatio<K, R>( + { model }: { model: Arrays.ArrayModel<K, R> } +): JSX.Element { const [filtered, total] = [model.getRowCount(), model.getTotalRowCount()]; const title = `${filtered} displayed messages / ${total} total messages`; return ( @@ -296,35 +307,38 @@ function FilterRatio<K, R>({ model }: { model: Arrays.ArrayModel<K, R> }) { // --- Messages Columns // -------------------------------------------------------------------------- -const renderKind: Renderer<logkind> = (kind: logkind) => { - const label = kind.toLocaleLowerCase(); - let icon = ''; - let color = 'black'; - switch (kind) { - case 'RESULT': icon = 'ANGLE.RIGHT'; break; - case 'FEEDBACK': icon = 'CIRC.INFO'; break; - case 'DEBUG': icon = 'HELP'; break; - case 'WARNING': icon = 'ATTENTION'; color = '#C00000'; break; - case 'ERROR': case 'FAILURE': icon = 'WARNING'; color = '#C00000'; break; - } - return <Icon title={label} id={icon} fill={color} />; -}; +const renderKind: Renderer<logkind> = + (kind: logkind): JSX.Element => { + const label = kind.toLocaleLowerCase(); + let icon = ''; + let color = 'black'; + switch (kind) { + case 'RESULT': icon = 'ANGLE.RIGHT'; break; + case 'FEEDBACK': icon = 'CIRC.INFO'; break; + case 'DEBUG': icon = 'HELP'; break; + case 'WARNING': icon = 'ATTENTION'; color = '#C00000'; break; + case 'ERROR': case 'FAILURE': icon = 'WARNING'; color = '#C00000'; break; + } + return <Icon title={label} id={icon} fill={color} />; + }; const renderCell: Renderer<string> = - (text: string) => (<Cell title={text}>{text}</Cell>); + (text: string): JSX.Element => (<Cell title={text}>{text}</Cell>); const renderMessage: Renderer<string> = - (text: string) => (<div title={text} className="message-cell"> {text} </div>); + (text: string): JSX.Element => + (<div title={text} className="message-cell"> {text} </div>); const renderDir: Renderer<Ast.source> = - (loc: Ast.source) => (<Cell label={loc.dir} title={loc.file} />); + (loc: Ast.source): JSX.Element => + (<Cell label={loc.dir} title={loc.file} />); const renderFile: Renderer<Ast.source> = - (loc: Ast.source) => ( + (loc: Ast.source): JSX.Element => ( <Cell label={`${loc.base}:${loc.line}`} title={loc.file} /> ); -const MessageColumns = () => ( +const MessageColumns = (): JSX.Element => ( <> <Column id="kind" @@ -403,10 +417,10 @@ const byMessage: Compare.ByFields<Message> = { const globalFilterState = new GlobalState(defaultFilter); -export default function RenderMessages() { +export default function RenderMessages(): JSX.Element { const [model] = React.useState(() => { - const f = (msg: Message) => msg.key; + const f = (msg: Message): string => msg.key; const m = new Arrays.CompactModel<string, Message>(f); m.setOrderingByFields(byMessage); return m; diff --git a/ivette/src/frama-c/kernel/Properties.tsx b/ivette/src/frama-c/kernel/Properties.tsx index 208002b85902bb9a4b3f1929b8ea1ebd0d12e2b9..fe050e3a81db8987a699001c3b4816aaa7f2ccc2 100644 --- a/ivette/src/frama-c/kernel/Properties.tsx +++ b/ivette/src/frama-c/kernel/Properties.tsx @@ -20,8 +20,6 @@ /* */ /* ************************************************************************ */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ - // -------------------------------------------------------------------------- // --- Properties // -------------------------------------------------------------------------- @@ -105,7 +103,7 @@ const DEFAULTS: { [key: string]: boolean } = { 'eva.ctrl_tainted_only': false, }; -function filter(path: string) { +function filter(path: string): boolean { const defaultValue = DEFAULTS[path] ?? true; return Settings.getWindowSettings( `ivette.properties.filter.${path}`, @@ -114,7 +112,7 @@ function filter(path: string) { ); } -function useFilter(path: string) { +function useFilter(path: string): [boolean, () => void] { const defaultValue = DEFAULTS[path] ?? true; return Dome.useFlipSettings( `ivette.properties.filter.${path}`, @@ -124,7 +122,7 @@ function useFilter(path: string) { function filterStatus( status: Properties.propStatus, -) { +): boolean { switch (status) { case 'valid': return filter('status.valid'); @@ -153,7 +151,7 @@ function filterStatus( function filterKind( kind: Properties.propKind, -) { +): boolean { switch (kind) { case 'assert': return filter('kind.assert'); case 'loop_invariant': return filter('kind.invariant'); @@ -173,7 +171,7 @@ function filterKind( } } -function filterAlarm(alarm: string | undefined) { +function filterAlarm(alarm: string | undefined): boolean { if (alarm) { if (!filter('alarms.alarms')) return false; switch (alarm) { @@ -202,7 +200,7 @@ function filterAlarm(alarm: string | undefined) { return filter('alarms.others'); } -function filterEva(p: Property) { +function filterEva(p: Property): boolean { let b = true; if (p.priority === false && filter('eva.priority_only')) b = false; @@ -224,7 +222,7 @@ function filterEva(p: Property) { return b; } -function filterProperty(p: Property) { +function filterProperty(p: Property): boolean { return filterStatus(p.status) && filterKind(p.kind) && filterAlarm(p.alarm) @@ -236,32 +234,33 @@ function filterProperty(p: Property) { // -------------------------------------------------------------------------- const renderCode: Renderer<string> = - (text: string) => (<Code className="code-column" title={text}>{text}</Code>); + (text: string): JSX.Element => + (<Code className="code-column" title={text}>{text}</Code>); const renderTag: Renderer<States.Tag> = - (d: States.Tag) => <Label label={d.label ?? d.name} title={d.descr} />; + (d: States.Tag): JSX.Element => + (<Label label={d.label ?? d.name} title={d.descr} />); const renderNames: Renderer<string[]> = - (names: string[]) => { + (names: string[]): JSX.Element | null => { const label = names?.join(': '); return (label ? <Label label={label} /> : null); }; const renderDir: Renderer<Ast.source> = - (loc: Ast.source) => ( - <Code className="code-column" label={loc.dir} title={loc.file} /> - ); + (loc: Ast.source): JSX.Element => + (<Code className="code-column" label={loc.dir} title={loc.file} />); const renderFile: Renderer<Ast.source> = - (loc: Ast.source) => ( - <Code className="code-column" label={loc.base} title={loc.file} /> - ); + (loc: Ast.source): JSX.Element => + (<Code className="code-column" label={loc.base} title={loc.file} />); const renderPriority: Renderer<boolean> = - (prio: boolean) => (prio ? <Icon id="ATTENTION" /> : null); + (prio: boolean): JSX.Element | null => + (prio ? <Icon id="ATTENTION" /> : null); const renderTaint: Renderer<States.Tag> = - (taint: States.Tag) => { + (taint: States.Tag): JSX.Element | null => { let id = null; let color = 'black'; switch (taint.name) { @@ -275,11 +274,11 @@ const renderTaint: Renderer<States.Tag> = return (id ? <Icon id={id} fill={color} title={taint.descr} /> : null); }; -function ColumnCode<Row>(props: ColumnProps<Row, string>) { +function ColumnCode<Row>(props: ColumnProps<Row, string>): JSX.Element { return <Column render={renderCode} {...props} />; } -function ColumnTag<Row>(props: ColumnProps<Row, States.Tag>) { +function ColumnTag<Row>(props: ColumnProps<Row, States.Tag>): JSX.Element { return <Column render={renderTag} {...props} />; } @@ -349,12 +348,12 @@ class PropertyModel this.setFilter(this.filterItem.bind(this)); } - setFilterFunction(kf?: string) { + setFilterFunction(kf?: string): void { this.filterFun = kf; if (filter('currentFunction')) this.reload(); } - filterItem(prop: Property) { + filterItem(prop: Property): boolean { const kf = prop.fct; const cf = this.filterFun; const filteringFun = cf && filter('currentFunction'); @@ -376,7 +375,7 @@ interface SectionProps { children: React.ReactNode; } -function Section(props: SectionProps) { +function Section(props: SectionProps): JSX.Element { const settings = `properties-section-${props.label}`; return ( <Folder @@ -396,9 +395,9 @@ interface CheckFieldProps { path: string; } -function CheckField(props: CheckFieldProps) { +function CheckField(props: CheckFieldProps): JSX.Element { const [value, setValue] = useFilter(props.path); - const onChange = () => { setValue(); Reload.emit(); }; + const onChange = (): void => { setValue(); Reload.emit(); }; return ( <Checkbox style={{ @@ -415,7 +414,7 @@ function CheckField(props: CheckFieldProps) { /* eslint-disable max-len */ -function PropertyFilter() { +function PropertyFilter(): JSX.Element { return ( <Scroll> <CheckField label="Current function" path="currentFunction" /> @@ -497,35 +496,29 @@ function PropertyFilter() { // --- Property Columns // ------------------------------------------------------------------------- -const PropertyColumns = () => { - +function PropertyColumns(): JSX.Element { const statusDict = States.useTags(Properties.propStatusTags); const kindDict = States.useTags(Properties.propKindTags); const alarmDict = States.useTags(Properties.alarmsTags); const taintDict = States.useTags(Eva.taintStatusTags); - const getStatus = React.useCallback( ({ status: st }: Property) => (statusDict.get(st) ?? { name: st }), [statusDict], ); - const getKind = React.useCallback( ({ kind: kd }: Property) => (kindDict.get(kd) ?? { name: kd }), [kindDict], ); - const getAlarm = React.useCallback( ({ alarm }: Property) => ( alarm === undefined ? alarm : (alarmDict.get(alarm) ?? { name: alarm }) ), [alarmDict], ); - const getTaint = React.useCallback( ({ taint }: Property) => (taintDict.get(taint) ?? { name: taint }), [taintDict], ); - return ( <> <Column @@ -587,10 +580,9 @@ const PropertyColumns = () => { /> </> ); +} -}; - -function FilterRatio({ model }: { model: PropertyModel }) { +function FilterRatio({ model }: { model: PropertyModel }): JSX.Element { Models.useModel(model); const [filtered, total] = [model.getRowCount(), model.getTotalRowCount()]; return ( @@ -608,7 +600,7 @@ function FilterRatio({ model }: { model: PropertyModel }) { // --- Properties Table // ------------------------------------------------------------------------- -export default function RenderProperties() { +export default function RenderProperties(): JSX.Element { // Hooks const model = React.useMemo(() => new PropertyModel(), []); diff --git a/ivette/src/frama-c/states.ts b/ivette/src/frama-c/states.ts index 14b03daf39dd0ed6e24699a0d9f9eda87b381733..a33f5fef80fe2914481c599c5953cf0ba0d13187 100644 --- a/ivette/src/frama-c/states.ts +++ b/ivette/src/frama-c/states.ts @@ -20,8 +20,6 @@ /* */ /* ************************************************************************ */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ - // -------------------------------------------------------------------------- // --- Frama-C States // -------------------------------------------------------------------------- @@ -91,7 +89,7 @@ Server.onShutdown(() => { * Current Project (Custom React Hook). * @return The current project. */ -export function useProject() { +export function useProject(): string | undefined { Dome.useUpdate(PROJECT); return currentProject; } @@ -104,7 +102,7 @@ export function useProject() { * Emits `PROJECT`. * @param project The project identifier. */ -export async function setProject(project: string) { +export async function setProject(project: string): Promise<void> { if (Server.isRunning()) { try { const sr: Server.SetRequest<string, null> = { @@ -162,11 +160,11 @@ export function useRequest<In, Out>( const footprint = project ? JSON.stringify([project, rq.name, params]) : undefined; - const update = (opt: Out | undefined | null) => { + const update = (opt: Out | undefined | null): void => { if (opt !== null) setResponse(opt); }; - async function trigger() { + async function trigger(): Promise<void> { if (project && rq && params !== undefined) { try { update(options.pending); @@ -285,14 +283,14 @@ class SyncState<A> { PROJECT.on(this.update); } - getValue() { + getValue(): A | undefined { if (!this.upToDate && Server.isRunning()) { this.update(); } return this.value; } - async setValue(v: A) { + async setValue(v: A): Promise<void> { try { this.upToDate = true; this.value = v; @@ -308,7 +306,7 @@ class SyncState<A> { } } - async update() { + async update(): Promise<void> { try { this.upToDate = true; this.value = undefined; @@ -390,11 +388,11 @@ class SyncArray<K, A> { this.update = this.update.bind(this); } - update() { + update(): void { if (!this.upToDate && Server.isRunning()) this.fetch(); } - async fetch() { + async fetch(): Promise<void> { if (this.fetching || !Server.isRunning()) return; try { this.fetching = true; @@ -423,7 +421,7 @@ class SyncArray<K, A> { } } - async reload() { + async reload(): Promise<void> { try { this.model.clear(); this.upToDate = false; @@ -466,7 +464,7 @@ Server.onShutdown(() => syncArrays.clear()); // -------------------------------------------------------------------------- /** Force a Synchronized Array to reload. */ -export function reloadArray<K, A>(arr: Array<K, A>) { +export function reloadArray<K, A>(arr: Array<K, A>): void { lookupSyncArray(arr).reload(); } @@ -796,7 +794,7 @@ export function useSelection(): [Selection, (a: SelectionActions) => void] { } /** Resets the selected locations. */ -export async function resetSelection() { +export async function resetSelection(): Promise<void> { GlobalSelection.setValue(emptySelection); if (Server.isRunning()) { try {