@ -1,3 +1,5 @@
/* 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 } from 'react' // eslint-disable-line
import Button from './Button/StaticAnalyserButton' // 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'
@ -69,6 +71,8 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
const [ basicEnabled , setBasicEnabled ] = useState ( true )
const [ basicEnabled , setBasicEnabled ] = useState ( true )
const [ solhintEnabled , setSolhintEnabled ] = useState ( true ) // assuming that solhint is always enabled
const [ solhintEnabled , setSolhintEnabled ] = useState ( true ) // assuming that solhint is always enabled
const [ showSlither , setShowSlither ] = useState ( false )
const [ showSlither , setShowSlither ] = useState ( false )
const [ slitherEnabled , setSlitherEnabled ] = useState ( false )
const [ startAnalysis , setStartAnalysis ] = useState ( false )
const [ isSupportedVersion , setIsSupportedVersion ] = useState ( false )
const [ isSupportedVersion , setIsSupportedVersion ] = useState ( false )
let [ showLibsWarning , setShowLibsWarning ] = useState ( false ) // eslint-disable-line prefer-const
let [ showLibsWarning , setShowLibsWarning ] = useState ( false ) // eslint-disable-line prefer-const
const [ categoryIndex , setCategoryIndex ] = useState ( groupedModuleIndex ( groupedModules ) )
const [ categoryIndex , setCategoryIndex ] = useState ( groupedModuleIndex ( groupedModules ) )
@ -109,16 +113,9 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
useEffect ( ( ) = > {
useEffect ( ( ) = > {
setWarningState ( { } )
setWarningState ( { } )
const runAnalysis = async ( ) = > {
const runAnalysis = async ( ) = > {
await run ( state . data , state . source , state . file , state , props , isSupportedVersion , showSlither , categoryIndex , groupedModules , runner , _paq , message , showWarnings , allWarnings , warningContainer , calculateWarningStateEntries , warningState , setHints , hints , setSlitherWarnings , setSsaWarnings )
await run ( state . data , state . source , state . file , state , props , isSupportedVersion , showSlither , categoryIndex , groupedModules , runner , _paq , message , showWarnings , allWarnings , warningContainer , calculateWarningStateEntries , warningState , setHints , hints , setSlitherWarnings , setSsaWarnings , slitherEnabled , setStartAnalysis )
}
}
props . event . trigger ( 'staticAnaysisWarning' , [ ] )
props . event . trigger ( 'staticAnaysisWarning' , [ ] )
// if (basicEnabled) {
// if (state.data !== null) {
// runAnalysis().catch(console.error);
// }
// } else {
// props.event.trigger('staticAnaysisWarning', [])
// }
return ( ) = > { }
return ( ) = > { }
} , [ state ] )
} , [ state ] )
@ -126,6 +123,17 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
props . analysisModule . call ( 'solidity' , 'getCompilerState' ) . then ( ( compilerState ) = > setDisableForRun ( compilerState . currentVersion ) )
props . analysisModule . call ( 'solidity' , 'getCompilerState' ) . then ( ( compilerState ) = > setDisableForRun ( compilerState . currentVersion ) )
} , [ ] )
} , [ ] )
useEffect ( ( ) = > {
const checkRemixdActive = async ( ) = > {
const remixdActive = await props . analysisModule . call ( 'manager' , 'isActive' , 'remixd' )
if ( remixdActive ) {
setSlitherEnabled ( true )
setShowSlither ( true )
}
}
checkRemixdActive ( )
} , [ props ] )
useEffect ( ( ) = > {
useEffect ( ( ) = > {
props . analysisModule . on ( 'filePanel' , 'setWorkspace' , ( currentWorkspace ) = > {
props . analysisModule . on ( 'filePanel' , 'setWorkspace' , ( currentWorkspace ) = > {
// Reset warning state
// Reset warning state
@ -138,9 +146,13 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
setSlitherWarnings ( [ ] )
setSlitherWarnings ( [ ] )
setSsaWarnings ( [ ] )
setSsaWarnings ( [ ] )
// Show 'Enable Slither Analysis' checkbox
// Show 'Enable Slither Analysis' checkbox
if ( currentWorkspace && currentWorkspace . isLocalhost === true ) setShowSlither ( true )
if ( currentWorkspace && currentWorkspace . isLocalhost === true ) {
setShowSlither ( true )
setSlitherEnabled ( true )
}
else {
else {
setShowSlither ( false )
setShowSlither ( false )
setSlitherEnabled ( false )
}
}
} )
} )
props . analysisModule . on ( 'manager' , 'pluginDeactivated' , ( plugin ) = > {
props . analysisModule . on ( 'manager' , 'pluginDeactivated' , ( plugin ) = > {
@ -150,6 +162,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
setWarningState ( [ ] )
setWarningState ( [ ] )
setHints ( [ ] )
setHints ( [ ] )
setSlitherWarnings ( [ ] )
setSlitherWarnings ( [ ] )
setSlitherEnabled ( false )
setSsaWarnings ( [ ] )
setSsaWarnings ( [ ] )
// Reset badge
// Reset badge
props . event . trigger ( 'staticAnaysisWarning' , [ ] )
props . event . trigger ( 'staticAnaysisWarning' , [ ] )
@ -203,6 +216,12 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
setWarningState ( newWarningState )
setWarningState ( newWarningState )
}
}
useEffect ( ( ) = > {
if ( hints . length > 0 ) {
props . event . trigger ( 'staticAnaysisWarning' , [ hints . length ] )
}
} , [ hints . length , state ] )
const showWarnings = ( warningMessage , groupByKey ) = > {
const showWarnings = ( warningMessage , groupByKey ) = > {
const resultArray = [ ]
const resultArray = [ ]
warningMessage . map ( x = > {
warningMessage . map ( x = > {
@ -359,7 +378,8 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
}
}
const hintErrors = hints . filter ( hint = > hint . type === 'error' )
const hintErrors = hints . filter ( hint = > hint . type === 'error' )
const slitherErrors = slitherWarnings . filter ( slitherError = > slitherError . options . type === 'error' )
const noLibSlitherWarnings = slitherWarnings . filter ( w = > ! w . options . isLibrary )
const slitherErrors = noLibSlitherWarnings . filter ( slitherError = > slitherError . options . type === 'error' )
const tabKeys = [
const tabKeys = [
{
{
@ -379,14 +399,12 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
? "alert alert-warning"
? "alert alert-warning"
: "alert alert-danger"
: "alert alert-danger"
} ` }
} ` }
style = { { cursor : "pointer" } }
style = { { cursor : "pointer" , overflow : 'hidden' , textOverflow : 'ellipsis' } }
>
< div
onClick = { async ( ) = > {
onClick = { async ( ) = > {
await props . analysisModule . call (
await props . analysisModule . call (
"editor" ,
"editor" ,
"discardHighlight"
"discardHighlight"
) ;
)
await props . analysisModule . call (
await props . analysisModule . call (
"editor" ,
"editor" ,
"highlight" ,
"highlight" ,
@ -406,6 +424,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
) ;
) ;
} }
} }
>
>
< div >
< span className = "text-wrap" >
< span className = "text-wrap" >
{ hint . formattedMessage }
{ hint . formattedMessage }
< / span >
< / span >
@ -419,9 +438,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
< div
< div
key = { index }
key = { index }
className = "alert alert-danger"
className = "alert alert-danger"
style = { { cursor : "pointer" } }
style = { { cursor : "pointer" , overflow : 'hidden' , textOverflow : 'ellipsis' } }
>
< div
onClick = { async ( ) = > {
onClick = { async ( ) = > {
await props . analysisModule . call (
await props . analysisModule . call (
"editor" ,
"editor" ,
@ -446,9 +463,11 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
) ;
) ;
} }
} }
>
>
< span className = "text-wrap" >
< div >
< span className = "text-wrap" style = { { overflow : 'hidden' , textOverflow : 'ellipsis' } } >
{ hint . formattedMessage }
{ hint . formattedMessage }
< / span >
< / span >
< br / >
< span > { hint . type } < / span >
< span > { hint . type } < / span >
< br / >
< br / >
< span > { ` ${ hint . column } : ${ hint . line } ` } < / span >
< span > { ` ${ hint . column } : ${ hint . line } ` } < / span >
@ -458,7 +477,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
< / Fragment >
< / Fragment >
< / div >
< / div >
< / div >
< / div >
) : < span className = "display-6" > No Results . < / span > }
) : state . data && state . file . length > 0 && state . source && startAnalysis && hints . length > 0 ? < span className = "ml-4 spinner-grow-sm d-flex justify-content-center" > Loading . . . < / span > : < span className = "display-6 text-center " > Nothing to report < / span > }
< / >
< / >
) ,
) ,
title : (
title : (
@ -466,11 +485,13 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
Linter
Linter
{ hints . length > 0 ? (
{ hints . length > 0 ? (
hideWarnings ? (
hideWarnings ? (
< i className = "badge badge-info rounded-circle ml-1" >
< i className = { ` badge ${ hints . filter ( x = > x . type === 'error' ) . length > 0
? ` badge-danger ` : 'badge-warning' } rounded - circle ml - 1 text - center ` }>
{ hintErrors . length }
{ hintErrors . length }
< / i >
< / i >
) : (
) : (
< i className = "badge badge-info rounded-circle ml-1" >
< i className = { ` badge ${ hints . filter ( x = > x . type === 'error' ) . length > 0
? ` badge-danger ` : 'badge-warning' } rounded - circle ml - 1 text - center ` }>
{ hints . length }
{ hints . length }
< / i >
< / i >
)
)
@ -493,9 +514,10 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
< div className = "mb-4 pt-2" >
< div className = "mb-4 pt-2" >
{ Object . entries ( warningState ) . map ( ( element , index ) = > (
{ Object . entries ( warningState ) . map ( ( element , index ) = > (
< div key = { index } >
< div key = { index } >
{ element [ 1 ] [ "map" ] (
{ ! hideWarnings && element [ 1 ] [ 'length' ] > 0 ? < span className = "text-dark h6" > { element [ 0 ] } < / span > : null }
{ ! hideWarnings ? element [ 1 ] [ "map" ] (
( x , i ) = > // eslint-disable-line dot-notation
( x , i ) = > // eslint-disable-line dot-notation
x . hasWarning && ! hideWarnings
x . hasWarning
? ( // eslint-disable-next-line dot-notation
? ( // eslint-disable-next-line dot-notation
< div
< div
data - id = { ` staticAnalysisModule ${ x . warningModuleName } ${ i } ` }
data - id = { ` staticAnalysisModule ${ x . warningModuleName } ${ i } ` }
@ -511,13 +533,30 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
/ >
/ >
< / div >
< / div >
) : null
) : null
) }
) : element [ 1 ] [ "map" ] (
( x , i ) = > // eslint-disable-line dot-notation
! x . hasWarning
? ( // eslint-disable-next-line dot-notation
< div
data - id = { ` staticAnalysisModule ${ x . warningModuleName } ${ i } ` }
id = { ` staticAnalysisModule ${ x . warningModuleName } ${ i } ` }
key = { i }
>
< ErrorRenderer
name = { ` staticAnalysisModule ${ x . warningModuleName } ${ i } ` }
message = { x . msg }
opt = { x . options }
warningErrors = { x . warningErrors }
editor = { props . analysisModule }
/ >
< / div >
) : null ) }
{ }
{ }
< / div >
< / div >
) ) }
) ) }
< / div >
< / div >
< / div >
< / div >
) : < span className = "display-6" > No Results . < / span > }
) : state . data && state . file . length > 0 && state . source && startAnalysis && Object . entries ( warningState ) . length > 0 ? < span className = "ml-4 spinner-grow-sm d-flex justify-content-center" > Loading . . . < / span > : < span className = "display-6 text-center " > Nothing to report < / span > }
< / >
< / >
) ,
) ,
} ,
} ,
@ -528,13 +567,17 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
Slither
Slither
{ slitherWarnings . length > 0 ? (
{ slitherWarnings . length > 0 ? (
hideWarnings ? (
hideWarnings ? (
< i className = "badge badge-info rounded-circle ml-1" >
< i className = "badge badge-warning rounded-circle ml-1" >
{ slitherErrors . length }
{ slitherErrors . length }
< / i >
< / i >
) : (
) : showLibsWarning === true && hideWarnings === false ? (
< i className = "badge badge-info rounded-circle ml-1" >
< i className = { ` badge ${ slitherErrors . length > 0 ? ` badge-danger ` : 'badge-warning' } rounded-circle ml-1 text-center ` } >
{ slitherWarnings . length }
{ slitherWarnings . length }
< / i >
< / i >
) : (
< i className = { ` badge ${ slitherErrors . length > 0 ? ` badge-danger ` : 'badge-warning' } rounded-circle ml-1 text-center ` } >
{ noLibSlitherWarnings . length }
< / i >
)
)
) : null }
) : null }
< / span >
< / span >
@ -546,7 +589,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
< div className = "mb-4 pt-2" >
< div className = "mb-4 pt-2" >
< Fragment >
< Fragment >
{ ! hideWarnings
{ ! hideWarnings
? showLibsWarning ? slitherWarnings . filter ( warning = > warning . isLibrary ) . map ( ( warning , index ) = > (
? showLibsWarning ? slitherWarnings . map ( ( warning , index ) = > (
< div
< div
data - id = { ` staticAnalysisModule ${ warning . warningModuleName } ${ index } ` }
data - id = { ` staticAnalysisModule ${ warning . warningModuleName } ${ index } ` }
id = { ` staticAnalysisModule ${ warning . warningModuleName } ${ index } ` }
id = { ` staticAnalysisModule ${ warning . warningModuleName } ${ index } ` }
@ -560,7 +603,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
editor = { props . analysisModule }
editor = { props . analysisModule }
/ >
/ >
< / div >
< / div >
) ) : s litherWarnings. map ( ( warning , index ) = > (
) ) : noLibS litherWarnings. map ( ( warning , index ) = > (
< div
< div
data - id = { ` staticAnalysisModule ${ warning . warningModuleName } ${ index } ` }
data - id = { ` staticAnalysisModule ${ warning . warningModuleName } ${ index } ` }
id = { ` staticAnalysisModule ${ warning . warningModuleName } ${ index } ` }
id = { ` staticAnalysisModule ${ warning . warningModuleName } ${ index } ` }
@ -593,7 +636,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
< / Fragment >
< / Fragment >
< / div >
< / div >
< / div >
< / div >
) : < span className = "display-6" > No Resul t < / span > }
) : state . data && state . file . length > 0 && state . source && startAnalysis && slitherWarnings . length > 0 ? < span className = "ml-4 spinner-grow-sm d-flex justify-content-center" > Loading . . . < / span > : < span className = "display-6 text-center " > Nothing to repor t < / span > }
< / >
< / >
) ,
) ,
} ,
} ,
@ -615,7 +658,7 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
< RemixUiCheckbox
< RemixUiCheckbox
id = "checkAllEntries"
id = "checkAllEntries"
inputType = "checkbox"
inputType = "checkbox"
title = "Remix analysis is a basic analysis tool for Remix Ide ."
title = "Remix analysis runs a basic analysis ."
checked = { Object . values ( groupedModules ) . map ( ( value : any ) = > {
checked = { Object . values ( groupedModules ) . map ( ( value : any ) = > {
return ( value . map ( x = > {
return ( value . map ( x = > {
return x . _index . toString ( )
return x . _index . toString ( )
@ -633,12 +676,12 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
< RemixUiCheckbox
< RemixUiCheckbox
id = "solhintstaticanalysis"
id = "solhintstaticanalysis"
inputType = "checkbox"
inputType = "checkbox"
title = "Run solh int static analysis."
title = "Linter runs SolH int static analysis."
onClick = { handleLinterEnabled }
onClick = { handleLinterEnabled }
checked = { solhintEnabled }
checked = { solhintEnabled }
label = "Linter"
label = "Linter"
onChange = { ( ) = > { } }
onChange = { ( ) = > { } }
tooltipPlacement = { 'top -start' }
tooltipPlacement = { 'bottom -start' }
optionalClassName = "mr-3"
optionalClassName = "mr-3"
/ >
/ >
@ -646,24 +689,32 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
id = "enableSlither"
id = "enableSlither"
inputType = "checkbox"
inputType = "checkbox"
onClick = { handleSlitherEnabled }
onClick = { handleSlitherEnabled }
checked = { showSlither }
checked = { showSlither && slitherEnabled }
disabled = { true }
disabled = { slitherEnabled === false }
tooltipPlacement = "bottom-start"
label = "Slither"
label = "Slither"
onChange = { ( ) = > { } }
onChange = { ( ) = > { } }
optionalClassName = "mr-3"
optionalClassName = "mr-3"
title = "To run Slither analysis, Remix IDE must be connected to your local filesystem with remixd."
title = { slitherEnabled ? "Slither runs Slither static analysis." : "To run Slither analysis, Remix IDE must be connected to your local filesystem with Remixd." }
/ >
/ >
< / div >
< / div >
< Button
{ state . data && state . file . length > 0 && state . source ? < Button
buttonText = { ` Analyse ${ state . file } ` }
buttonText = { ` Analyse ${ state . file } ` }
classList = "btn btn-sm btn-primary btn-block"
onClick = { async ( ) = > await run ( state . data , state . source , state . file , state , props , isSupportedVersion , showSlither , categoryIndex , groupedModules , runner , _paq ,
message , showWarnings , allWarnings , warningContainer , calculateWarningStateEntries , warningState , setHints , hints , setSlitherWarnings , setSsaWarnings , slitherEnabled , setStartAnalysis ) }
disabled = { ( state . data === null || ! isSupportedVersion ) || ( ! solhintEnabled && ! basicEnabled ) }
/ > : < B u t t o n
buttonText = { ` Analyze ${ state . file } ` }
title = { ` ${ runButtonTitle } ` }
title = { ` ${ runButtonTitle } ` }
classList = "btn btn-sm btn-primary btn-block"
classList = "btn btn-sm btn-primary btn-block"
onClick = { async ( ) = > await run ( state . data , state . source , state . file , state , props , isSupportedVersion , showSlither , categoryIndex , groupedModules , runner , _paq ,
onClick = { async ( ) = > await run ( state . data , state . source , state . file , state , props , isSupportedVersion , showSlither , categoryIndex , groupedModules , runner , _paq ,
message , showWarnings , allWarnings , warningContainer , calculateWarningStateEntries , warningState , setHints , hints , setSlitherWarnings , setSsaWarnings ) }
message , showWarnings , allWarnings , warningContainer , calculateWarningStateEntries , warningState , setHints , hints , setSlitherWarnings , setSsaWarnings , slitherEnabled , setStartAnalysis ) }
disabled = { ( state . data === null || ! isSupportedVersion ) || ( ! solhintEnabled && ! basicEnabled ) }
disabled = { ( state . data === null || ! isSupportedVersion ) || ( ! solhintEnabled && ! basicEnabled ) }
/ >
/ > }
{ state && state . data !== null && state . source !== null && state . file . length > 0 ? ( < div className = "d-flex border-top flex-column" >
{ state && state . data !== null && state . source !== null && state . file . length > 0 ? ( < div className = "d-flex border-top flex-column" >
< div className = "mt-4 p-2 d-flex border-top flex-column" >
{ slitherWarnings . length > 0 || hints . length > 0 || Object . entries ( warningState ) . length > 0 ? (
< div className = { ` mt-4 p-2 d-flex ${ slitherWarnings . length > 0 || hints . length > 0 || Object . entries ( warningState ) . length > 0 ? 'border-top' : '' } flex-column ` } >
< span > Last results for : < / span >
< span > Last results for : < / span >
< span
< span
className = "text-break break-word word-break font-weight-bold"
className = "text-break break-word word-break font-weight-bold"
@ -672,12 +723,12 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
{ state . file }
{ state . file }
< / span >
< / span >
< / div >
< / div >
< div className = "border-top mt-3 pt-2 mb-2" id = "staticanalysisresult" >
) : null }
< div className = "border-top mt-2 pt-2 mb-2" id = "staticanalysisresult" >
< RemixUiCheckbox
< RemixUiCheckbox
id = "showLibWarnings"
id = "showLibWarnings"
name = "showLibWarnings"
name = "showLibWarnings"
categoryId = "showLibWarnings"
categoryId = "showLibWarnings"
title = "When checked, the results are also displayed for external contract libraries."
inputType = "checkbox"
inputType = "checkbox"
checked = { showLibsWarning }
checked = { showLibsWarning }
label = "Show warnings for external libraries"
label = "Show warnings for external libraries"
@ -688,7 +739,6 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
< RemixUiCheckbox
< RemixUiCheckbox
id = "hideWarnings"
id = "hideWarnings"
name = "hideWarnings"
name = "hideWarnings"
title = "When checked, general warnings from analysis are hidden."
inputType = "checkbox"
inputType = "checkbox"
checked = { hideWarnings }
checked = { hideWarnings }
label = "Hide warnings"
label = "Hide warnings"
@ -696,13 +746,16 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
onChange = { ( ) = > { } }
onChange = { ( ) = > { } }
/ >
/ >
< / div >
< / div >
< Tabs defaultActiveKey = { tabKeys [ 0 ] . tabKey } >
< Tabs
defaultActiveKey = { tabKeys [ 0 ] . tabKey }
className = "px-1"
>
{
{
checkBasicStatus ( ) ? < Tab
checkBasicStatus ( ) ? < Tab
key = { tabKeys [ 1 ] . tabKey }
key = { tabKeys [ 1 ] . tabKey }
title = { tabKeys [ 1 ] . title }
title = { tabKeys [ 1 ] . title }
eventKey = { tabKeys [ 1 ] . tabKey }
eventKey = { tabKeys [ 1 ] . tabKey }
tabClassName = "text-decoration-none font-weight-bold"
tabClassName = "text-decoration-none font-weight-bold px-2 "
>
>
{ tabKeys [ 1 ] . child }
{ tabKeys [ 1 ] . child }
< / Tab > : null
< / Tab > : null
@ -711,15 +764,15 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
key = { tabKeys [ 0 ] . tabKey }
key = { tabKeys [ 0 ] . tabKey }
title = { tabKeys [ 0 ] . title }
title = { tabKeys [ 0 ] . title }
eventKey = { tabKeys [ 0 ] . tabKey }
eventKey = { tabKeys [ 0 ] . tabKey }
tabClassName = "text-decoration-none font-weight-bold"
tabClassName = "text-decoration-none font-weight-bold px-2 "
>
>
{ tabKeys [ 0 ] . child }
{ tabKeys [ 0 ] . child }
< / Tab > : null }
< / Tab > : null }
{ showSlither ? < Tab
{ showSlither && slitherEnabled ? < Tab
key = { tabKeys [ 2 ] . tabKey }
key = { tabKeys [ 2 ] . tabKey }
title = { tabKeys [ 2 ] . title }
title = { tabKeys [ 2 ] . title }
eventKey = { tabKeys [ 2 ] . tabKey }
eventKey = { tabKeys [ 2 ] . tabKey }
tabClassName = "text-decoration-none font-weight-bold"
tabClassName = "text-decoration-none font-weight-bold px-2 "
>
>
{ tabKeys [ 2 ] . child }
{ tabKeys [ 2 ] . child }
< / Tab > : null }
< / Tab > : null }