diff --git a/apps/remix-ide/src/app.js b/apps/remix-ide/src/app.js index d767019df4..57885170c5 100644 --- a/apps/remix-ide/src/app.js +++ b/apps/remix-ide/src/app.js @@ -56,6 +56,8 @@ import { xtermPlugin } from './app/plugins/electron/xtermPlugin' import { ripgrepPlugin } from './app/plugins/electron/ripgrepPlugin' import { compilerLoaderPlugin, compilerLoaderPluginDesktop } from './app/plugins/electron/compilerLoaderPlugin' import { appUpdaterPlugin } from './app/plugins/electron/appUpdaterPlugin' +import { SlitherHandleDesktop } from './app/plugins/electron/slitherPlugin' +import { SlitherHandle } from './app/files/slither-handle' import {SolCoder} from './app/plugins/solcoderAI' const isElectron = require('is-electron') @@ -376,6 +378,10 @@ class AppComponent { const compilerloader = isElectron()? new compilerLoaderPluginDesktop(): new compilerLoaderPlugin() this.engine.register([compilerloader]) + // slither analyzer plugin (remixd / desktop) + const slitherPlugin = isElectron() ? new SlitherHandleDesktop() : new SlitherHandle() + this.engine.register([slitherPlugin]) + // LAYOUT & SYSTEM VIEWS const appPanel = new MainPanel() Registry.getInstance().put({api: this.mainview, name: 'mainview'}) @@ -433,7 +439,6 @@ class AppComponent { filePanel.hardhatHandle, filePanel.foundryHandle, filePanel.truffleHandle, - filePanel.slitherHandle, linkLibraries, deployLibraries, openZeppelinProxy, @@ -500,7 +505,7 @@ class AppComponent { await this.appManager.activatePlugin(['solidity-script', 'remix-templates']) if (isElectron()){ - await this.appManager.activatePlugin(['isogit', 'electronconfig', 'electronTemplates', 'xterm', 'ripgrep', 'appUpdater']) + await this.appManager.activatePlugin(['isogit', 'electronconfig', 'electronTemplates', 'xterm', 'ripgrep', 'appUpdater', 'slither']) } this.appManager.on( diff --git a/apps/remix-ide/src/app/panels/file-panel.js b/apps/remix-ide/src/app/panels/file-panel.js index a709692452..b4fbb92397 100644 --- a/apps/remix-ide/src/app/panels/file-panel.js +++ b/apps/remix-ide/src/app/panels/file-panel.js @@ -9,7 +9,6 @@ import {PluginViewWrapper} from '@remix-ui/helper' const { HardhatHandle } = require('../files/hardhat-handle.js') const { FoundryHandle } = require('../files/foundry-handle.js') const { TruffleHandle } = require('../files/truffle-handle.js') -const { SlitherHandle } = require('../files/slither-handle.js') /* Overview of APIs: @@ -72,7 +71,7 @@ module.exports = class Filepanel extends ViewPlugin { this.hardhatHandle = new HardhatHandle() this.foundryHandle = new FoundryHandle() this.truffleHandle = new TruffleHandle() - this.slitherHandle = new SlitherHandle() + this.workspaces = [] this.appManager = appManager this.currentWorkspaceMetadata = null diff --git a/apps/remix-ide/src/app/plugins/electron/slitherPlugin.ts b/apps/remix-ide/src/app/plugins/electron/slitherPlugin.ts new file mode 100644 index 0000000000..b08cca82c2 --- /dev/null +++ b/apps/remix-ide/src/app/plugins/electron/slitherPlugin.ts @@ -0,0 +1,13 @@ +import { ElectronPlugin } from '@remixproject/engine-electron'; + +export class SlitherHandleDesktop extends ElectronPlugin { + constructor() { + super({ + displayName: 'slither', + name: 'slither', + description: 'electron slither', + methods: ['analyse'] + }) + this.methods = ['analyse'] + } +} diff --git a/apps/remix-ide/src/remixAppManager.js b/apps/remix-ide/src/remixAppManager.js index cf88f1a5bb..f16579ef6b 100644 --- a/apps/remix-ide/src/remixAppManager.js +++ b/apps/remix-ide/src/remixAppManager.js @@ -158,7 +158,7 @@ export class RemixAppManager extends PluginManager { this.pluginsDirectory = 'https://raw.githubusercontent.com/ethereum/remix-plugins-directory/master/build/metadata.json' this.pluginLoader = new PluginLoader() if (Registry.getInstance().get('platform').api.isDesktop()) { - requiredModules = [...requiredModules, 'fs', 'electronTemplates', 'isogit', 'remix-templates', 'electronconfig', 'xterm', 'compilerloader', 'ripgrep'] + requiredModules = [...requiredModules, 'fs', 'electronTemplates', 'isogit', 'remix-templates', 'electronconfig', 'xterm', 'compilerloader', 'ripgrep', 'slither'] } } diff --git a/apps/remixdesktop/src/engine.ts b/apps/remixdesktop/src/engine.ts index 2a92603534..6a00ceab31 100644 --- a/apps/remixdesktop/src/engine.ts +++ b/apps/remixdesktop/src/engine.ts @@ -9,6 +9,7 @@ import { ConfigPlugin } from './plugins/configPlugin'; import { TemplatesPlugin } from './plugins/templates'; import { RipgrepPlugin } from './plugins/ripgrepPlugin'; import { CompilerLoaderPlugin } from './plugins/compilerLoader'; +import { SlitherPlugin } from './plugins/slitherPlugin'; import { AppUpdaterPlugin } from './plugins/appUpdater'; const engine = new Engine() @@ -20,6 +21,7 @@ const configPlugin = new ConfigPlugin() const templatesPlugin = new TemplatesPlugin() const ripgrepPlugin = new RipgrepPlugin() const compilerLoaderPlugin = new CompilerLoaderPlugin() +const slitherPlugin = new SlitherPlugin() const appUpdaterPlugin = new AppUpdaterPlugin() engine.register(appManager) @@ -30,6 +32,7 @@ engine.register(configPlugin) engine.register(templatesPlugin) engine.register(ripgrepPlugin) engine.register(compilerLoaderPlugin) +engine.register(slitherPlugin) engine.register(appUpdaterPlugin) appManager.activatePlugin('electronconfig') diff --git a/apps/remixdesktop/src/lib/remixd.ts b/apps/remixdesktop/src/lib/remixd.ts new file mode 100644 index 0000000000..4315da2880 --- /dev/null +++ b/apps/remixdesktop/src/lib/remixd.ts @@ -0,0 +1,36 @@ +import { ElectronBasePluginClient } from "@remixproject/plugin-electron"; +import { Profile } from "@remixproject/plugin-utils"; + +export class ElectronBasePluginRemixdClient extends ElectronBasePluginClient { + log: (...message: any) => void + error: (...message: any) => void + + currentSharedFolder: string = '' + constructor(webContentsId: number, profile: Profile) { + super(webContentsId, profile); + this.log = (...message: any) => { + for(const m of message) { + this.call('terminal', 'log', { + type: 'log', + value: m + }) + } + } + this.error = (...message: any) => { + for(const m of message) { + this.call('terminal', 'log', { + type: 'error', + value: m + }) + } + } + + + this.onload(async () => { + this.on('fs' as any, 'workingDirChanged', async (path: string) => { + this.currentSharedFolder = path + }) + this.currentSharedFolder = await this.call('fs' as any, 'getWorkingDir') + }) + } + } \ No newline at end of file diff --git a/apps/remixdesktop/src/lib/slither.ts b/apps/remixdesktop/src/lib/slither.ts new file mode 100644 index 0000000000..3ff55f80dd --- /dev/null +++ b/apps/remixdesktop/src/lib/slither.ts @@ -0,0 +1,193 @@ +import { existsSync, readFileSync, readdirSync, unlinkSync } from 'fs' +import * as utils from './utils' +const { spawn, execSync } = require('child_process') // eslint-disable-line +export interface OutputStandard { + description: string + title: string + confidence: string + severity: string + sourceMap: any + category?: string + reference?: string + example?: any + [key: string]: any +} + +export const SlitherClientMixin = (Base) => class extends Base { + methods: Array + currentSharedFolder: string + + constructor(...args: any[]) { + super(...args); // Ensure the parent constructor is called + } + + + log(...message: any) { + if (this.log) { + this.log(...message) + } else { + console.log(...message) + } + } + + error(...message: any) { + if (this.error) { + this.error(...message) + } else { + console.error(...message) + } + } + + + mapNpmDepsDir(list) { + const remixNpmDepsPath = utils.absolutePath('.deps/npm', this.currentSharedFolder) + const localNpmDepsPath = utils.absolutePath('node_modules', this.currentSharedFolder) + const npmDepsExists = existsSync(remixNpmDepsPath) + const nodeModulesExists = existsSync(localNpmDepsPath) + let isLocalDep = false + let isRemixDep = false + let allowPathString = '' + let remapString = '' + + for (const e of list) { + const importPath = e.replace(/import ['"]/g, '').trim() + const packageName = importPath.split('/')[0] + if (nodeModulesExists && readdirSync(localNpmDepsPath).includes(packageName)) { + isLocalDep = true + remapString += `${packageName}=./node_modules/${packageName} ` + } else if (npmDepsExists && readdirSync(remixNpmDepsPath).includes(packageName)) { + isRemixDep = true + remapString += `${packageName}=./.deps/npm/${packageName} ` + } + } + if (isLocalDep) allowPathString += './node_modules,' + if (isRemixDep) allowPathString += './.deps/npm,' + + return { remapString, allowPathString } + } + + transform(detectors: Record[]): OutputStandard[] { + const standardReport: OutputStandard[] = [] + for (const e of detectors) { + const obj = {} as OutputStandard + obj.description = e.description + obj.title = e.check + obj.confidence = e.confidence + obj.severity = e.impact + obj.sourceMap = e.elements.map((element) => { + delete element.source_mapping.filename_used + delete element.source_mapping.filename_absolute + return element + }) + standardReport.push(obj) + } + return standardReport + } + + analyse(filePath: string, compilerConfig: Record) { + return new Promise((resolve, reject) => { + const options = { cwd: this.currentSharedFolder, shell: true } + + const { currentVersion, optimize, evmVersion } = compilerConfig + if (currentVersion && currentVersion.includes('+commit')) { + // Get compiler version with commit id e.g: 0.8.2+commit.661d110 + const versionString: string = currentVersion.substring(0, currentVersion.indexOf('+commit') + 16) + this.log(`[Slither Analysis]: Compiler version is ${versionString}`) + let solcOutput: Buffer + // Check solc current installed version + try { + solcOutput = execSync('solc --version', options) + } catch (err) { + this.error(err) + reject(new Error('Error in running solc command')) + } + if (!solcOutput.toString().includes(versionString)) { + this.log('[Slither Analysis]: Compiler version is different from installed solc version') + // Get compiler version without commit id e.g: 0.8.2 + const version: string = versionString.substring(0, versionString.indexOf('+commit')) + // List solc versions installed using solc-select + try { + const solcSelectInstalledVersions: Buffer = execSync('solc-select versions', options) + // Check if required version is already installed + if (!solcSelectInstalledVersions.toString().includes(version)) { + this.log(`[Slither Analysis]: Installing ${version} using solc-select`) + // Install required version + execSync(`solc-select install ${version}`, options) + } + this.log(`[Slither Analysis]: Setting ${version} as current solc version using solc-select`) + // Set solc current version as required version + execSync(`solc-select use ${version}`, options) + } catch (err) { + this.error(err) + reject(new Error('Error in running solc-select command')) + } + } else this.log('[Slither Analysis]: Compiler version is same as installed solc version') + } + // Allow paths and set solc remapping for import URLs + const fileContent = readFileSync(utils.absolutePath(filePath, this.currentSharedFolder), 'utf8') + const importsArr = fileContent.match(/import ['"][^.|..](.+?)['"];/g) + let remaps = '' + if (importsArr?.length) { + const { remapString } = this.mapNpmDepsDir(importsArr) + remaps = remapString.trim() + } + const optimizeOption: string = optimize ? '--optimize' : '' + const evmOption: string = evmVersion ? `--evm-version ${evmVersion}` : '' + let solcArgs = '' + if (optimizeOption) { + solcArgs += optimizeOption + ' ' + } + if (evmOption) { + if (!solcArgs.endsWith(' ')) solcArgs += ' ' + solcArgs += evmOption + } + if (solcArgs) { + solcArgs = `--solc-args "${solcArgs.trimStart()}"` + } + const solcRemaps = remaps ? `--solc-remaps "${remaps}"` : '' + + const outputFile = 'remix-slither-report.json' + try { + // We don't keep the previous analysis + const outputFilePath = utils.absolutePath(outputFile, this.currentSharedFolder) + if (existsSync(outputFilePath)) unlinkSync(outputFilePath) + } catch (e) { + this.error('unable to remove the output file') + this.error(e.message) + } + const cmd = `slither ${filePath} ${solcArgs} ${solcRemaps} --json ${outputFile}` + this.log('[Slither Analysis]: Running Slither...') + // Added `stdio: 'ignore'` as for contract with NPM imports analysis which is exported in 'stderr' + // get too big and hangs the process. We process analysis from the report file only + const child = spawn(cmd, { cwd: this.currentSharedFolder, shell: true, stdio: 'ignore' }) + + const response = {} + child.on('close', () => { + const outputFileAbsPath: string = utils.absolutePath(outputFile, this.currentSharedFolder) + // Check if slither report file exists + if (existsSync(outputFileAbsPath)) { + let report = readFileSync(outputFileAbsPath, 'utf8') + report = JSON.parse(report) + if (report['success']) { + response['status'] = true + if (!report['results'] || !report['results'].detectors || !report['results'].detectors.length) { + response['count'] = 0 + } else { + const { detectors } = report['results'] + response['count'] = detectors.length + response['data'] = this.transform(detectors) + } + + resolve(response) + } else { + this.log(report['error']) + reject(new Error('Error in running Slither Analysis.')) + } + } else { + this.error('Error in generating Slither Analysis Report. Make sure Slither is properly installed.') + reject(new Error('Error in generating Slither Analysis Report. Make sure Slither is properly installed.')) + } + }) + }) + } +} diff --git a/apps/remixdesktop/src/lib/utils.ts b/apps/remixdesktop/src/lib/utils.ts new file mode 100644 index 0000000000..e406b647b9 --- /dev/null +++ b/apps/remixdesktop/src/lib/utils.ts @@ -0,0 +1,24 @@ +import * as pathModule from 'path' +/** + * returns the absolute path of the given @arg path + * + * @param {String} path - relative path (Unix style which is the one used by Remix IDE) + * @param {String} sharedFolder - absolute shared path. platform dependent representation. + * @return {String} platform dependent absolute path (/home/user1/.../... for unix, c:\user\...\... for windows) + */ +function absolutePath (path: string, sharedFolder:string): string { + path = normalizePath(path) + path = pathModule.resolve(sharedFolder, path) + return path +} +function normalizePath (path) { + if (path === '/') path = './' + if (process.platform === 'win32') { + return path.replace(/\//g, '\\') + } + return path +} + +export { absolutePath } + + diff --git a/apps/remixdesktop/src/plugins/slitherPlugin.ts b/apps/remixdesktop/src/plugins/slitherPlugin.ts new file mode 100644 index 0000000000..5819e0a46b --- /dev/null +++ b/apps/remixdesktop/src/plugins/slitherPlugin.ts @@ -0,0 +1,34 @@ +import { Profile } from "@remixproject/plugin-utils"; +import { ElectronBasePlugin, ElectronBasePluginClient } from "@remixproject/plugin-electron" + +import { ElectronBasePluginRemixdClient } from "../lib/remixd" +import { SlitherClientMixin } from "../lib/slither"; +const profile: Profile = { + name: 'slither', + displayName: 'electron slither', + description: 'electron slither', +} + +export class SlitherPlugin extends ElectronBasePlugin { + clients: any [] + constructor() { + super(profile, clientProfile, SlitherClientMixin(SlitherPluginClient)) + this.methods = [...super.methods] + } +} + +const clientProfile: Profile = { + name: 'slither', + displayName: 'electron slither', + description: 'electron slither', + methods: ['analyse'] +} + + +class SlitherPluginClient extends ElectronBasePluginRemixdClient { + constructor(webContentsId: number, profile: Profile) { + super(webContentsId, profile); + } +} + + diff --git a/apps/remixdesktop/src/preload.ts b/apps/remixdesktop/src/preload.ts index c5c8e5a65d..0f15dfab51 100644 --- a/apps/remixdesktop/src/preload.ts +++ b/apps/remixdesktop/src/preload.ts @@ -6,7 +6,7 @@ console.log('preload.ts', new Date().toLocaleTimeString()) /* preload script needs statically defined API for each plugin */ -const exposedPLugins = ['fs', 'git', 'xterm', 'isogit', 'electronconfig', 'electronTemplates', 'ripgrep', 'compilerloader', 'appUpdater'] +const exposedPLugins = ['fs', 'git', 'xterm', 'isogit', 'electronconfig', 'electronTemplates', 'ripgrep', 'compilerloader', 'appUpdater', 'slither'] let webContentsId: number | undefined diff --git a/apps/remixdesktop/test/tests/app/slitherlinux.test.ts b/apps/remixdesktop/test/tests/app/slitherlinux.test.ts new file mode 100644 index 0000000000..2fc7aaba59 --- /dev/null +++ b/apps/remixdesktop/test/tests/app/slitherlinux.test.ts @@ -0,0 +1,95 @@ +import {NightwatchBrowser} from 'nightwatch' +import { ChildProcess, spawn } from 'child_process' +import { homedir } from 'os' +const tests = { + before: function (browser: NightwatchBrowser, done: VoidFunction) { + done() + }, + open: function (browser: NightwatchBrowser) { + browser.waitForElementVisible('*[data-id="openFolderButton"]', 10000).click('*[data-id="openFolderButton"]') + }, + + 'open default template': function (browser: NightwatchBrowser) { + browser + .waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000) + .waitForElementVisible('button[data-id="landingPageImportFromTemplate"]') + .click('button[data-id="landingPageImportFromTemplate"]') + .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') + .waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') + .click('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') + .pause(3000) + .windowHandles(function (result) { + console.log(result.value) + browser.switchWindow(result.value[1]) + .waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]') + .click('*[data-id="treeViewLitreeViewItemtests"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]') + .click('*[data-id="treeViewLitreeViewItemcontracts"]') + .waitForElementVisible('[data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]') + .openFile('contracts/1_Storage.sol') + .waitForElementVisible('*[id="editorView"]', 10000) + .getEditorValue((content) => { + browser.assert.ok(content.includes('function retrieve() public view returns (uint256){')) + }) + }) + }, + 'Should install slither #group6': function (browser: NightwatchBrowser) { + browser.perform(async (done) => { + await installSlither() + done() + }) + }, + 'run slither': function (browser: NightwatchBrowser) { + browser + .click('[data-id="verticalIconsKindpluginManager"]') + .scrollAndClick('[data-id="pluginManagerComponentActivateButtonsolidityStaticAnalysis"]') + .clickLaunchIcon('solidity').click('*[data-id="compilerContainerCompileBtn"]') + .pause(1000) + .clickLaunchIcon('solidityStaticAnalysis') + .useXpath() + .click('//*[@id="staticAnalysisRunBtn"]') + .waitForElementPresent('//*[@id="staticanalysisresult"]', 5000) + .waitForElementVisible({ + selector: "//*[@data-id='nolibslitherwarnings'][contains(text(), '1')]", + locateStrategy: 'xpath', + timeout: 5000 + }) + .waitForElementVisible({ + selector: "//div[@data-id='block']/span[contains(text(), '1 warnings found.')]", + locateStrategy: 'xpath', + timeout: 5000 + }) + }, + + after: function (browser: NightwatchBrowser) { + browser.end() + }, +} + +async function installSlither(): Promise { + console.log('installSlither', process.cwd()) + try { + const server = spawn('node', ['../../dist/libs/remixd/src/scripts/installSlither.js'], { cwd: process.cwd(), shell: true, detached: true }) + return new Promise((resolve, reject) => { + server.stdout.on('data', function (data) { + console.log(data.toString()) + if ( + data.toString().includes("Slither is ready to use") + ) { + console.log('resolving') + resolve() + } + }) + server.stderr.on('err', function (data) { + console.log(data.toString()) + reject(data.toString()) + }) + }) + } catch (e) { + console.log(e) + } + } + +module.exports = { + ...process.platform.startsWith('linux')?tests:{} +} \ No newline at end of file diff --git a/libs/remix-ui/static-analyser/src/lib/remix-ui-static-analyser.tsx b/libs/remix-ui/static-analyser/src/lib/remix-ui-static-analyser.tsx index 7b3241481d..ca2913c1f6 100644 --- a/libs/remix-ui/static-analyser/src/lib/remix-ui-static-analyser.tsx +++ b/libs/remix-ui/static-analyser/src/lib/remix-ui-static-analyser.tsx @@ -1,23 +1,24 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-unused-vars */ -import React, {useEffect, useState, useReducer, useRef, Fragment} from 'react' // eslint-disable-line +import React, {useEffect, useState, useReducer, useRef, Fragment, useContext} from 'react' // eslint-disable-line import Button from './Button/StaticAnalyserButton' // eslint-disable-line -import { util } from '@remix-project/remix-lib' +import {util} from '@remix-project/remix-lib' import _ from 'lodash' import * as semver from 'semver' import {TreeView, TreeViewItem} from '@remix-ui/tree-view' // eslint-disable-line import {RemixUiCheckbox} from '@remix-ui/checkbox' // eslint-disable-line import ErrorRenderer from './ErrorRenderer' // eslint-disable-line -import { compilation } from './actions/staticAnalysisActions' -import { initialState, analysisReducer } from './reducers/staticAnalysisReducer' -import { CodeAnalysis } from '@remix-project/remix-analyzer' +import {compilation} from './actions/staticAnalysisActions' +import {initialState, analysisReducer} from './reducers/staticAnalysisReducer' +import {CodeAnalysis} from '@remix-project/remix-analyzer' import Tab from 'react-bootstrap/Tab' import Tabs from 'react-bootstrap/Tabs' -import { AnalysisTab, SolHintReport } from '../staticanalyser' -import { run } from './actions/staticAnalysisActions' -import { BasicTitle, calculateWarningStateEntries } from './components/BasicTitle' -import { Nav, TabContainer } from 'react-bootstrap' -import { CustomTooltip } from '@remix-ui/helper' +import {AnalysisTab, SolHintReport} from '../staticanalyser' +import {run} from './actions/staticAnalysisActions' +import {BasicTitle, calculateWarningStateEntries} from './components/BasicTitle' +import {Nav, TabContainer} from 'react-bootstrap' +import {CustomTooltip} from '@remix-ui/helper' +import { appPlatformTypes, platformContext } from '@remix-ui/app' declare global { interface Window { @@ -37,6 +38,7 @@ type tabSelectionType = 'remix' | 'solhint' | 'slither' | 'none' export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => { const [runner] = useState(new CodeAnalysis()) + const platform = useContext(platformContext) const preProcessModules = (arr: any) => { return arr.map((Item, i) => { @@ -130,7 +132,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => { useEffect(() => { const checkRemixdActive = async () => { - const remixdActive = await props.analysisModule.call('manager', 'isActive', 'remixd') + const remixdActive = await props.analysisModule.call('manager', 'isActive', 'remixd') || platform === appPlatformTypes.desktop if (remixdActive) { setSlitherEnabled(true) setShowSlither(true) @@ -147,12 +149,12 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => { // Reset badge // Reset state - dispatch({ type: '', payload: initialState }) + dispatch({type: '', payload: initialState}) setHints([]) setSlitherWarnings([]) setSsaWarnings([]) // Show 'Enable Slither Analysis' checkbox - if (currentWorkspace && currentWorkspace.isLocalhost === true) { + if ((currentWorkspace && currentWorkspace.isLocalhost === true) || platform === appPlatformTypes.desktop) { setShowSlither(true) setSlitherEnabled(true) } else { @@ -175,7 +177,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => { // Reset badge props.event.trigger('staticAnalysisWarning', [-1]) // Reset state - dispatch({ type: '', payload: initialState }) + dispatch({type: '', payload: initialState}) setShowSlither(false) } }) @@ -319,7 +321,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => { label={