refactor: added custom checkbox for static analyser plugin

pull/5370/head
tizah 4 years ago committed by tizah
parent 339f2589a6
commit 8112f60705
  1. 49
      apps/remix-ide/src/app/tabs/analysis-tab.js
  2. 4
      libs/remix-ui/static-analyser/.babelrc
  3. 19
      libs/remix-ui/static-analyser/.eslintrc
  4. 7
      libs/remix-ui/static-analyser/README.md
  5. 1
      libs/remix-ui/static-analyser/src/index.ts
  6. 23
      libs/remix-ui/static-analyser/src/lib/Button/StaticAnalyserButton.tsx
  7. 45
      libs/remix-ui/static-analyser/src/lib/Checkbox/StaticAnalyserCheckedBox.tsx
  8. 76
      libs/remix-ui/static-analyser/src/lib/ErrorRenderer.tsx
  9. 381
      libs/remix-ui/static-analyser/src/lib/remix-ui-static-analyser.tsx
  10. 16
      libs/remix-ui/static-analyser/tsconfig.json
  11. 13
      libs/remix-ui/static-analyser/tsconfig.lib.json
  12. 3
      nx.json
  13. 6
      package-lock.json
  14. 1
      package.json
  15. 5
      tsconfig.json
  16. 19
      workspace.json

@ -1,9 +1,11 @@
import React from 'react' // eslint-disable-line
import { ViewPlugin } from '@remixproject/engine-web'
import ReactDOM from 'react-dom'
import { EventEmitter } from 'events'
import {RemixUiStaticAnalyser} from '@remix-ui/static-analyser' // eslint-disable-line
import * as packageJson from '../../../../../package.json'
var Renderer = require('../ui/renderer')
var yo = require('yo-yo')
var StaticAnalysis = require('./staticanalysis/staticAnalysisView')
var EventManager = require('../../lib/events')
const profile = {
@ -25,24 +27,39 @@ class AnalysisTab extends ViewPlugin {
this.event = new EventManager()
this.events = new EventEmitter()
this.registry = registry
this.element = document.createElement('div')
this.element.setAttribute('id', 'staticAnalyserView')
this._components = {
renderer: new Renderer(this)
}
this._components.registry = this.registry
this._deps = {
offsetToLineColumnConverter: this.registry.get(
'offsettolinecolumnconverter').api
}
}
onActivation () {
this.renderComponent()
}
render () {
this.staticanalysis = new StaticAnalysis(this.registry, this)
this.staticanalysis.event.register('staticAnaysisWarning', (count) => {
if (count > 0) {
this.emit('statusChanged', { key: count, title: `${count} warning${count === 1 ? '' : 's'}`, type: 'warning' })
} else if (count === 0) {
this.emit('statusChanged', { key: 'succeed', title: 'no warning', type: 'success' })
} else {
// count ==-1 no compilation result
this.emit('statusChanged', { key: 'none' })
}
})
this.registry.put({ api: this.staticanalysis, name: 'staticanalysis' })
return yo`<div class="px-3 pb-1" id="staticanalysisView">${this.staticanalysis.render()}</div>`
return this.element
}
renderComponent () {
ReactDOM.render(
<RemixUiStaticAnalyser
analysisRunner={this.runner}
registry={this.registry}
staticanalysis={this.staticanalysis}
analysisModule={this}
event={this.event}
/>,
this.element
)
}
}
module.exports = AnalysisTab

@ -0,0 +1,4 @@
{
"presets": ["@nrwl/react/babel"],
"plugins": []
}

@ -0,0 +1,19 @@
{
"env": {
"browser": true,
"es6": true
},
"extends": "../../../.eslintrc",
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 11,
"sourceType": "module"
},
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "error"
}
}

@ -0,0 +1,7 @@
# remix-ui-static-analyser
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test remix-ui-static-analyser` to execute the unit tests via [Jest](https://jestjs.io).

@ -0,0 +1 @@
export * from './lib/remix-ui-static-analyser'

@ -0,0 +1,23 @@
import React from 'react' //eslint-disable-line
interface StaticAnalyserButtonProps {
onClick: (event) => void
buttonText: string,
disabled?: boolean
}
const StaticAnalyserButton = ({
onClick,
buttonText,
disabled
}: StaticAnalyserButtonProps) => {
return (
<div className="remixui-button-container">
<button className="btn btn-sm w-31 btn-primary" onClick={onClick} disabled={disabled}>
{buttonText}
</button>
</div>
)
}
export default StaticAnalyserButton

@ -0,0 +1,45 @@
import React from 'react' //eslint-disable-line
interface StaticAnalyserCheckBoxProps {
onClick?: (event) => void
onChange?: (event) => void
label?: string
inputType?: string
name?: string
checked?: boolean
id?: string
itemName?: string
categoryId?: string
}
const StaticAnalyserCheckedBox = ({
id,
label,
onClick,
inputType,
name,
checked,
onChange,
itemName,
categoryId
}: StaticAnalyserCheckBoxProps) => {
return (
<div className="pt-1 h-80 mx-3 align-items-center listenOnNetwork_2A0YE0 custom-control custom-checkbox " onClick={onClick}>
<input
id={id}
type={inputType}
onChange={onChange}
style={{ verticalAlign: 'bottom' }}
name={name}
className="custom-control-input"
checked={checked}
/>
<label className="pt-1 form-check-label custom-control-label" id={`heading${categoryId}`} >
{name ? <h6>{itemName}</h6> : ''}
<p>{label}</p>
</label>
</div>
)
}
export default StaticAnalyserCheckedBox

@ -0,0 +1,76 @@
import React, { useState } from 'react' //eslint-disable-line
interface ErrorRendererProps {
message: any;
opt: any,
warningErrors: any
}
const ErrorRenderer = ({ message, opt }: ErrorRendererProps) => {
const [, setError] = useState(
{
row: null,
column: null,
text: null,
type: null,
errFile: null
}
)
const getPositionDetails = (msg: any) => {
const result = { } as any
// To handle some compiler warning without location like SPDX license warning etc
if (!msg.includes(':')) return { errLine: -1, errCol: -1, errFile: msg }
// extract line / column
let position = msg.match(/^(.*?):([0-9]*?):([0-9]*?)?/)
result.errLine = position ? parseInt(position[2]) - 1 : -1
result.errCol = position ? parseInt(position[3]) : -1
// extract file
position = msg.match(/^(https:.*?|http:.*?|.*?):/)
result.errFile = position ? position[1] : ''
return result
}
if (!message) return
let position = getPositionDetails(message)
if (!position.errFile || (opt.errorType && opt.errorType === position.errFile)) {
// Updated error reported includes '-->' before file details
const errorDetails = message.split('-->')
// errorDetails[1] will have file details
if (errorDetails.length > 1) position = getPositionDetails(errorDetails[1])
}
opt.errLine = position.errLine
opt.errCol = position.errCol
opt.errFile = position.errFile.trim()
if (!opt.noAnnotations && opt.errFile) {
setError({
errFile: opt.errFile,
row: opt.errLine,
column: opt.errCol,
text: message,
type: opt.type
})
}
const classList = opt.type === 'error' ? 'alert alert-danger' : 'alert alert-warning'
return (
<div>
<div className={`sol ${opt.type} ${classList}`}>
<div className="close" data-id="renderer">
<i className="fas fa-times"></i>
</div>
<span className='d-flex flex-column'>
<span className='h6 font-weight-bold'>{opt.item.name}</span>
{ opt.item.warning }
{opt.item.more
? <span><a href={opt.item.more} target='_blank'>more</a></span>
: <span> </span>
}
<span title={`Position in ${opt.errFile}`}>Pos: {opt.locationString}</span>
</span>
</div>
</div>
)
}
export default ErrorRenderer

@ -0,0 +1,381 @@
import React, { useEffect, useState } from 'react'
import CheckBox from './Checkbox/StaticAnalyserCheckedBox' // eslint-disable-line
import Button from './Button/StaticAnalyserButton' // eslint-disable-line
import remixLib from '@remix-project/remix-lib'
import _ from 'lodash'
import { TreeView, TreeViewItem } from '@remix-ui/tree-view' // eslint-disable-line
import ErrorRenderer from './ErrorRenderer' // eslint-disable-line
const StaticAnalysisRunner = require('@remix-project/remix-analyzer').CodeAnalysis
const utils = remixLib.util
/* eslint-disable-next-line */
export interface RemixUiStaticAnalyserProps {
renderStaticAnalysis: any
staticanalysis: any
analysisRunner: any,
lastCompilationResult: any,
lastCompilationSource: any,
registry: any,
event: any,
analysisModule: any
_deps: any
}
export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
const [runner] = useState(new StaticAnalysisRunner())
const preProcessModules = (arr: any) => {
return arr.map((Item, i) => {
const itemObj = new Item()
itemObj._index = i
itemObj.categoryDisplayName = itemObj.category.displayName
itemObj.categoryId = itemObj.category.id
return itemObj
})
}
const groupedModules = utils.groupBy(
preProcessModules(runner.modules()),
'categoryId'
)
const getIndex = (modules, array) => {
Object.values(modules).map((value: {_index}) => {
if (Array.isArray(value)) {
value.forEach((x) => {
array.push(x._index.toString())
})
} else {
array.push(value._index.toString())
}
})
}
const groupedModuleIndex = (modules) => {
const indexOfCategory = []
if (!_.isEmpty(modules)) {
getIndex(modules, indexOfCategory)
}
return indexOfCategory
}
const [categoryIndex, setCategoryIndex] = useState(groupedModuleIndex(groupedModules))
const warningContainer = React.useRef(null)
const [runButtonState, setRunButtonState] = useState(true)
const [autoRun, setAutoRun] = useState(true)
const [result, setResult] = useState({
lastCompilationResult: null,
lastCompilationSource: null,
currentFile: 'No file compiled'
})
const [, setModuleNameResult] = useState(null)
const [, setWarning] = useState({
msg: '',
options: {},
hasWarning: false,
warningErrors: []
})
const [warningState, setWarningState] = useState([])
useEffect(() => {
if (autoRun) {
const setCompilationResult = async (data, source, file) => {
await setResult({ lastCompilationResult: data, lastCompilationSource: source, currentFile: file })
}
if (props.analysisModule) {
console.log({ autoRun })
props.analysisModule.on(
'solidity',
'compilationFinished',
(file, source, languageVersion, data) => {
if (languageVersion.indexOf('soljson') !== 0) return
setCompilationResult(data, source, file)
run(data, source, file)
}
)
}
}
return () => { }
}, [autoRun])
const run = (lastCompilationResult, lastCompilationSource, currentFile) => {
// const highlightLocation = async (location, fileName) => {
// await props.analysisModule.call('editor', 'discardHighlight')
// await props.analysisModule.call('editor', 'highlight', location, fileName)
// }
setResult({ lastCompilationResult, lastCompilationSource, currentFile })
if (lastCompilationResult && categoryIndex.length) {
setRunButtonState(false)
let warningCount = 0
const warningMessage = []
runner.run(lastCompilationResult, categoryIndex, results => {
results.map((result) => {
let moduleName
Object.keys(groupedModules).map(key => {
groupedModules[key].forEach(el => {
if (el.name === result.name) {
moduleName = groupedModules[key][0].categoryDisplayName
}
})
})
setModuleNameResult(moduleName)
const warningErrors = []
result.report.map((item) => {
let location: any = {}
let locationString = 'not available'
let column = 0
let row = 0
let fileName = currentFile
if (item.location) {
var split = item.location.split(':')
var file = split[2]
location = {
start: parseInt(split[0]),
length: parseInt(split[1])
}
location = props.analysisModule._deps.offsetToLineColumnConverter.offsetToLineColumn(
location,
parseInt(file),
lastCompilationSource.sources,
lastCompilationResult.sources
)
row = location.start.line
column = location.start.column
locationString = row + 1 + ':' + column + ':'
fileName = Object.keys(lastCompilationResult.contracts)[file]
}
warningCount++
const msg = `
<span class='d-flex flex-column'>
<span class='h6 font-weight-bold'>${result.name}</span>
${item.warning}
${item.more
? `<span><a href=${item.more} target='_blank'>more</a></span>`
: '<span> </span>'
}
<span class="" title="Position in ${fileName}">Pos: ${locationString}</span>
</span>`
const options = {
type: 'warning',
useSpan: true,
errFile: fileName,
errLine: row,
errCol: column,
item: item,
name: result.name,
locationString,
more: item.more
}
warningErrors.push(options)
setWarning({ msg, hasWarning: true, options, warningErrors: warningErrors })
warningMessage.push({ msg, options, hasWarning: true, warningModuleName: moduleName })
})
})
const resultArray = []
warningMessage.map(x => {
resultArray.push(x)
})
function groupBy (objectArray, property) {
return objectArray.reduce((acc, obj) => {
const key = obj[property]
if (!acc[key]) {
acc[key] = []
}
// Add object to list for given key's value
acc[key].push(obj)
return acc
}, {})
}
const groupedCategory = groupBy(resultArray, 'warningModuleName')
setWarningState(groupedCategory)
})
props.event.trigger('staticAnaysisWarning', [warningCount])
} else {
setRunButtonState(true)
if (categoryIndex.length) {
warningContainer.current.innerText = 'No compiled AST available'
}
props.event.trigger('staticAnaysisWarning', [-1])
}
}
// const correctRunBtnDisabled = () => {
// if (props.lastCompilationResult && selectedCategoryIndex.length !== 0) {
// setRunButtonState(false)
// } else {
// setRunButtonState(true)
// }
// }
const handleCheckAllModules = (groupedModules) => {
const index = groupedModuleIndex(groupedModules)
if (index.every(el => categoryIndex.includes(el))) {
setCategoryIndex(
categoryIndex.filter((el) => {
return !index.includes(el)
})
)
} else {
setCategoryIndex(_.uniq([...categoryIndex, ...index]))
}
}
const handleCheckOrUncheckCategory = (category) => {
const index = groupedModuleIndex(category)
if (index.every(el => categoryIndex.includes(el))) {
setCategoryIndex(
categoryIndex.filter((el) => {
return !index.includes(el)
})
)
} else {
setCategoryIndex(_.uniq([...categoryIndex, ...index]))
}
}
const handleAutoRun = () => {
if (autoRun) {
setAutoRun(false)
} else {
setAutoRun(true)
}
console.log(' auton run function')
}
const handleCheckSingle = (event, _index) => {
_index = _index.toString()
if (categoryIndex.includes(_index)) {
setCategoryIndex(categoryIndex.filter(val => val !== _index))
} else {
setCategoryIndex(_.uniq([...categoryIndex, _index]))
}
}
const categoryItem = (categoryId, item, i) => {
return (
<div className="form-check" key={i}>
<CheckBox
categoryId={categoryId}
id={`staticanalysismodule_${categoryId}_${i}`}
inputType="checkbox"
name="checkSingleEntry"
itemName={item.name}
label={item.description}
onClick={event => handleCheckSingle(event, item._index)}
checked={categoryIndex.includes(item._index.toString())}
/>
</div>
)
}
const categorySection = (category, categoryId, i) => {
return (
<div className="" key={i}>
<div className="block">
<TreeView>
<TreeViewItem
label={
<label
htmlFor={`heading${categoryId}`}
style={{ cursor: 'pointer' }}
className="pl-3 card-header h6 d-flex justify-content-between font-weight-bold border-left px-1 py-2 w-100"
data-bs-toggle="collapse"
data-bs-expanded="false"
data-bs-controls={`heading${categoryId}`}
data-bs-target={`#heading${categoryId}`}
>
{category[0].categoryDisplayName}
<div>
<i className="fas fa-angle-double-right"></i>
</div>
</label>
}
expand={true}
>
<div>
<CheckBox onClick={() => handleCheckOrUncheckCategory(category)} id={categoryId} inputType="checkbox" label={`Select ${category[0].categoryDisplayName}`} name='checkCategoryEntry' checked={category.map(x => x._index.toString()).every(el => categoryIndex.includes(el))} />
</div>
<div className="w-100 d-block px-2 my-1 entries collapse multi-collapse" id={`heading${categoryId}`}>
{category.map((item, i) => {
return (
categoryItem(categoryId, item, i)
)
})}
</div>
</TreeViewItem>
</TreeView>
</div>
</div>
)
}
return (
<div style={{ marginLeft: '10px', marginRight: '10px' }}>
<div className="my-2 d-flex flex-column align-items-left">
<div className="d-flex justify-content-between">
<div >
<CheckBox
id="checkAllEntries"
inputType="checkbox"
checked={Object.values(groupedModules).map((value: any) => {
return (value.map(x => {
return x._index.toString()
}))
}).flat().every(el => categoryIndex.includes(el))}
label="Select all"
onClick={() => handleCheckAllModules(groupedModules)}
/>
</div>
<div className="" >
<CheckBox
id="autorunstaticanalysis"
inputType="checkbox"
onClick={handleAutoRun}
checked={autoRun}
label="Autorun"
/>
</div>
<Button buttonText="Run" onClick={() => run(result.lastCompilationResult, result.lastCompilationSource, result.currentFile)} disabled={runButtonState || categoryIndex.length === 0}/>
</div>
</div>
<div id="staticanalysismodules" className="list-group list-group-flush">
{Object.keys(groupedModules).map((categoryId, i) => {
const category = groupedModules[categoryId]
return (
categorySection(category, categoryId, i)
)
})
}
</div>
<div className="mt-2 p-2 d-flex border-top flex-column">
<span>last results for:</span>
<span
className="text-break break-word word-break font-weight-bold"
id="staticAnalysisCurrentFile"
>
{result.currentFile && result.currentFile}
</span>
</div>
<div className="" >
<div className="mb-4" >
{
(Object.entries(warningState).map((element) => (
<>
<span className="text-dark h6">{element[0]}</span>
{element[1].map(x => (
x.hasWarning ? (<ErrorRenderer message={x.msg} opt={x.options} warningErrors={ x.warningErrors}/>) : null
))}
</>
)))
}
</div>
</div>
</div>
)
}
export default RemixUiStaticAnalyser

@ -0,0 +1,16 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"jsx": "react",
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}

@ -0,0 +1,13 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../dist/out-tsc",
"types": ["node"]
},
"files": [
"../../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
"../../../node_modules/@nrwl/react/typings/image.d.ts"
],
"exclude": ["**/*.spec.ts", "**/*.spec.tsx"],
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
}

@ -95,6 +95,9 @@
},
"remix-ui-workspace": {
"tags": []
},
"remix-ui-static-analyser": {
"tags": []
}
}
}

6
package-lock.json generated

@ -25142,9 +25142,9 @@
}
},
"lodash": {
"version": "4.17.19",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ=="
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"lodash-es": {
"version": "4.17.15",

@ -159,6 +159,7 @@
"isbinaryfile": "^3.0.2",
"jquery": "^3.3.1",
"jszip": "^3.6.0",
"lodash": "^4.17.21",
"merge": "^1.2.0",
"npm-install-version": "^6.0.2",
"react": "16.13.1",

@ -11,7 +11,7 @@
"target": "es2015",
"module": "commonjs",
"typeRoots": ["node_modules/@types"],
"lib": ["es2017", "dom"],
"lib": ["es2017", "es2019", "dom"],
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"baseUrl": ".",
@ -38,7 +38,8 @@
"@remix-ui/modal-dialog": ["libs/remix-ui/modal-dialog/src/index.ts"],
"@remix-ui/toaster": ["libs/remix-ui/toaster/src/index.ts"],
"@remix-ui/file-explorer": ["libs/remix-ui/file-explorer/src/index.ts"],
"@remix-ui/workspace": ["libs/remix-ui/workspace/src/index.ts"]
"@remix-ui/workspace": ["libs/remix-ui/workspace/src/index.ts"],
"@remix-ui/static-analyser": ["libs/remix-ui/static-analyser/src/index.ts"]
}
},
"exclude": ["node_modules", "tmp"]

@ -725,6 +725,25 @@
}
}
}
},
"remix-ui-static-analyser": {
"root": "libs/remix-ui/static-analyser",
"sourceRoot": "libs/remix-ui/static-analyser/src",
"projectType": "library",
"schematics": {},
"architect": {
"lint": {
"builder": "@nrwl/linter:lint",
"options": {
"linter": "eslint",
"tsConfig": ["libs/remix-ui/static-analyser/tsconfig.lib.json"],
"exclude": [
"**/node_modules/**",
"!libs/remix-ui/static-analyser/**/*"
]
}
}
}
}
},
"cli": {

Loading…
Cancel
Save