diff --git a/apps/remix-ide-e2e/src/tests/staticAnalysis.spec.ts b/apps/remix-ide-e2e/src/tests/staticAnalysis.spec.ts
index a68a4a24ce..504148b266 100644
--- a/apps/remix-ide-e2e/src/tests/staticAnalysis.spec.ts
+++ b/apps/remix-ide-e2e/src/tests/staticAnalysis.spec.ts
@@ -40,7 +40,7 @@ function runTests (browser: NightwatchBrowser) {
.pause(10000)
.testContracts('Untitled.sol', sources[0]['Untitled.sol'], ['TooMuchGas', 'test1', 'test2'])
.clickLaunchIcon('solidityStaticAnalysis')
- .click('#staticanalysisView button')
+ .click('#staticanalysisButton button')
.waitForElementPresent('#staticanalysisresult .warning', 2000, true, function () {
listSelectorContains(['Use of tx.origin',
'Fallback function of contract TooMuchGas requires too much gas',
diff --git a/apps/remix-ide/src/app/tabs/analysis-tab.js b/apps/remix-ide/src/app/tabs/analysis-tab.js
index 4406ba875e..b3bba2705c 100644
--- a/apps/remix-ide/src/app/tabs/analysis-tab.js
+++ b/apps/remix-ide/src/app/tabs/analysis-tab.js
@@ -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,23 +27,49 @@ 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 this.element
+ }
- return yo`
${this.staticanalysis.render()}
`
+ renderComponent () {
+ ReactDOM.render(
+ ,
+ this.element,
+ () => {
+ this.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' })
+ }
+ })
+ }
+ )
}
}
diff --git a/apps/remix-ide/src/app/tabs/staticanalysis/staticAnalysisView.js b/apps/remix-ide/src/app/tabs/staticanalysis/staticAnalysisView.js
deleted file mode 100644
index 668adab89e..0000000000
--- a/apps/remix-ide/src/app/tabs/staticanalysis/staticAnalysisView.js
+++ /dev/null
@@ -1,302 +0,0 @@
-'use strict'
-var StaticAnalysisRunner = require('@remix-project/remix-analyzer').CodeAnalysis
-var yo = require('yo-yo')
-var $ = require('jquery')
-var remixLib = require('@remix-project/remix-lib')
-var utils = remixLib.util
-var css = require('./styles/staticAnalysisView-styles')
-var Renderer = require('../../ui/renderer')
-const SourceHighlighter = require('../../editor/sourceHighlighter')
-
-var EventManager = require('../../../lib/events')
-
-function staticAnalysisView (localRegistry, analysisModule) {
- var self = this
- this.event = new EventManager()
- this.view = null
- this.runner = new StaticAnalysisRunner()
- this.modulesView = this.renderModules()
- this.lastCompilationResult = null
- this.lastCompilationSource = null
- this.currentFile = 'No file compiled'
- this.sourceHighlighter = new SourceHighlighter()
- this.analysisModule = analysisModule
- self._components = {
- renderer: new Renderer(analysisModule)
- }
- self._components.registry = localRegistry
- // dependencies
- self._deps = {
- offsetToLineColumnConverter: self._components.registry.get('offsettolinecolumnconverter').api
- }
-
- analysisModule.on('solidity', 'compilationFinished', (file, source, languageVersion, data) => {
- self.lastCompilationResult = null
- self.lastCompilationSource = null
- if (languageVersion.indexOf('soljson') !== 0) return
- self.lastCompilationResult = data
- self.lastCompilationSource = source
- self.currentFile = file
- self.correctRunBtnDisabled()
- if (self.view && self.view.querySelector('#autorunstaticanalysis').checked) {
- self.run()
- }
- })
-}
-
-staticAnalysisView.prototype.render = function () {
- this.runBtn = yo``
- const view = yo`
-
-
-
- ${this.modulesView}
-
-
- last results for:
- ${this.currentFile}
-
-
-
- `
-
- if (!this.view) {
- this.view = view
- }
- this.correctRunBtnDisabled()
- return view
-}
-
-staticAnalysisView.prototype.selectedModules = function () {
- if (!this.view) {
- return []
- }
- const selected = this.view.querySelectorAll('[name="staticanalysismodule"]:checked')
- var toRun = []
- for (var i = 0; i < selected.length; i++) {
- toRun.push(selected[i].attributes.index.value)
- }
- return toRun
-}
-
-staticAnalysisView.prototype.run = function () {
- if (!this.view) {
- return
- }
- const highlightLocation = async (location, fileName) => {
- await this.analysisModule.call('editor', 'discardHighlight')
- await this.analysisModule.call('editor', 'highlight', location, fileName)
- }
- const selected = this.selectedModules()
- const warningContainer = $('#staticanalysisresult')
- warningContainer.empty()
- this.view.querySelector('#staticAnalysisCurrentFile').innerText = this.currentFile
- var self = this
- if (this.lastCompilationResult && selected.length) {
- this.runBtn.removeAttribute('disabled')
- let warningCount = 0
- this.runner.run(this.lastCompilationResult, selected, (results) => {
- const groupedModules = utils.groupBy(preProcessModules(this.runner.modules()), 'categoryId')
- results.map((result, j) => {
- let moduleName
- Object.keys(groupedModules).map((key) => {
- groupedModules[key].forEach((el) => {
- if (el.name === result.name) {
- moduleName = groupedModules[key][0].categoryDisplayName
- }
- })
- })
- const alreadyExistedEl = this.view.querySelector(`[id="staticAnalysisModule${moduleName}"]`)
- if (!alreadyExistedEl) {
- warningContainer.append(`
-
- ${moduleName}
-
- `)
- }
-
- result.report.map((item, i) => {
- let location = ''
- let locationString = 'not available'
- let column = 0
- let row = 0
- let fileName = this.currentFile
- if (item.location) {
- var split = item.location.split(':')
- var file = split[2]
- location = {
- start: parseInt(split[0]),
- length: parseInt(split[1])
- }
- location = self._deps.offsetToLineColumnConverter.offsetToLineColumn(
- location,
- parseInt(file),
- self.lastCompilationSource.sources,
- self.lastCompilationResult.sources
- )
- row = location.start.line
- column = location.start.column
- locationString = (row + 1) + ':' + column + ':'
- fileName = Object.keys(self.lastCompilationResult.contracts)[file]
- }
- warningCount++
- const msg = yo`
-
- ${result.name}
- ${item.warning}
- ${item.more ? yo`more` : yo``}
- Pos: ${locationString}
- `
- self._components.renderer.error(
- msg,
- this.view.querySelector(`[id="staticAnalysisModule${moduleName}"]`),
- {
- click: () => highlightLocation(location, fileName),
- type: 'warning',
- useSpan: true,
- errFile: fileName,
- errLine: row,
- errCol: column
- }
- )
- })
- })
- // hide empty staticAnalysisModules sections
- this.view.querySelectorAll('[name="staticAnalysisModules"]').forEach((section) => {
- if (!section.getElementsByClassName('alert-warning').length) section.hidden = true
- })
- self.event.trigger('staticAnaysisWarning', [warningCount])
- })
- } else {
- this.runBtn.setAttribute('disabled', 'disabled')
- if (selected.length) {
- warningContainer.html('No compiled AST available')
- }
- self.event.trigger('staticAnaysisWarning', [-1])
- }
-}
-staticAnalysisView.prototype.checkModule = function (event) {
- const selected = this.view.querySelectorAll('[name="staticanalysismodule"]:checked')
- const checkAll = this.view.querySelector('[id="checkAllEntries"]')
- this.correctRunBtnDisabled()
- if (event.target.checked) {
- checkAll.checked = true
- } else if (!selected.length) {
- checkAll.checked = false
- }
-}
-staticAnalysisView.prototype.correctRunBtnDisabled = function () {
- if (!this.view) {
- return
- }
- const selected = this.view.querySelectorAll('[name="staticanalysismodule"]:checked')
- if (this.lastCompilationResult && selected.length !== 0) {
- this.runBtn.removeAttribute('disabled')
- } else {
- this.runBtn.setAttribute('disabled', 'disabled')
- }
-}
-staticAnalysisView.prototype.checkAll = function (event) {
- if (!this.view) {
- return
- }
- // checks/unchecks all
- const checkBoxes = this.view.querySelectorAll('[name="staticanalysismodule"]')
- checkBoxes.forEach((checkbox) => { checkbox.checked = event.target.checked })
- this.correctRunBtnDisabled()
-}
-
-staticAnalysisView.prototype.handleCollapse = function (e) {
- const downs = e.toElement.parentElement.getElementsByClassName('fas fa-angle-double-right')
- const iEls = document.getElementsByTagName('i')
- for (var i = 0; i < iEls.length; i++) { iEls[i].hidden = false }
- downs[0].hidden = true
-}
-
-staticAnalysisView.prototype.renderModules = function () {
- const groupedModules = utils.groupBy(preProcessModules(this.runner.modules()), 'categoryId')
- const moduleEntries = Object.keys(groupedModules).map((categoryId, i) => {
- const category = groupedModules[categoryId]
- const entriesDom = category.map((item, i) => {
- return yo`
-
- `
- })
- return yo`
-
-
this.handleCollapse(e)}"/>
-
-
- ${entriesDom}
-
-
- `
- })
- // collaps first module
- moduleEntries[0].getElementsByTagName('input')[0].checked = true
- moduleEntries[0].getElementsByTagName('i')[0].hidden = true
- return yo`
-
- ${moduleEntries}
-
`
-}
-
-module.exports = staticAnalysisView
-
-/**
- * @dev Process & categorize static analysis modules to show them on UI
- * @param arr list of static analysis modules received from remix-analyzer module
- */
-function preProcessModules (arr) {
- return arr.map((Item, i) => {
- const itemObj = new Item()
- itemObj._index = i
- itemObj.categoryDisplayName = itemObj.category.displayName
- itemObj.categoryId = itemObj.category.id
- return itemObj
- })
-}
diff --git a/apps/remix-ide/src/app/tabs/staticanalysis/styles/staticAnalysisView-styles.js b/apps/remix-ide/src/app/tabs/staticanalysis/styles/staticAnalysisView-styles.js
deleted file mode 100644
index bd277a03aa..0000000000
--- a/apps/remix-ide/src/app/tabs/staticanalysis/styles/staticAnalysisView-styles.js
+++ /dev/null
@@ -1,36 +0,0 @@
-var csjs = require('csjs-inject')
-
-var css = csjs`
- .analysis {
- display: flex;
- flex-direction: column;
- }
- .result {
- margin-top: 1%;
- max-height: 300px;
- word-break: break-word;
- }
- .buttons {
- margin: 1rem 0;
- }
- .label {
- display: flex;
- align-items: center;
- }
- .label {
- display: flex;
- align-items: center;
- user-select: none;
- }
- .block input[type='radio']:checked ~ .entries{
- height: auto;
- transition: .5s ease-in;
- }
- .entries{
- height: 0;
- overflow: hidden;
- transition: .5s ease-out;
- }
-`
-
-module.exports = css
diff --git a/libs/remix-ui/checkbox/.babelrc b/libs/remix-ui/checkbox/.babelrc
new file mode 100644
index 0000000000..09d67939cc
--- /dev/null
+++ b/libs/remix-ui/checkbox/.babelrc
@@ -0,0 +1,4 @@
+{
+ "presets": ["@nrwl/react/babel"],
+ "plugins": []
+}
diff --git a/libs/remix-ui/checkbox/.eslintrc b/libs/remix-ui/checkbox/.eslintrc
new file mode 100644
index 0000000000..dae5c6feeb
--- /dev/null
+++ b/libs/remix-ui/checkbox/.eslintrc
@@ -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"
+ }
+}
diff --git a/libs/remix-ui/checkbox/README.md b/libs/remix-ui/checkbox/README.md
new file mode 100644
index 0000000000..56f9b617b0
--- /dev/null
+++ b/libs/remix-ui/checkbox/README.md
@@ -0,0 +1,7 @@
+# remix-ui-checkbox
+
+This library was generated with [Nx](https://nx.dev).
+
+## Running unit tests
+
+Run `nx test remix-ui-checkbox` to execute the unit tests via [Jest](https://jestjs.io).
diff --git a/libs/remix-ui/checkbox/src/index.ts b/libs/remix-ui/checkbox/src/index.ts
new file mode 100644
index 0000000000..27b694c6bd
--- /dev/null
+++ b/libs/remix-ui/checkbox/src/index.ts
@@ -0,0 +1 @@
+export * from './lib/remix-ui-checkbox'
diff --git a/libs/remix-ui/checkbox/src/lib/remix-ui-checkbox.css b/libs/remix-ui/checkbox/src/lib/remix-ui-checkbox.css
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/libs/remix-ui/checkbox/src/lib/remix-ui-checkbox.tsx b/libs/remix-ui/checkbox/src/lib/remix-ui-checkbox.tsx
new file mode 100644
index 0000000000..5535a05971
--- /dev/null
+++ b/libs/remix-ui/checkbox/src/lib/remix-ui-checkbox.tsx
@@ -0,0 +1,47 @@
+import React from 'react' //eslint-disable-line
+import './remix-ui-checkbox.css'
+
+/* eslint-disable-next-line */
+export interface RemixUiCheckboxProps {
+ onClick?: (event) => void
+ onChange?: (event) => void
+ label?: string
+ inputType?: string
+ name?: string
+ checked?: boolean
+ id?: string
+ itemName?: string
+ categoryId?: string
+}
+
+export const RemixUiCheckbox = ({
+ id,
+ label,
+ onClick,
+ inputType,
+ name,
+ checked,
+ onChange,
+ itemName,
+ categoryId
+}: RemixUiCheckboxProps) => {
+ return (
+
+
+
+
+ )
+}
+
+export default RemixUiCheckbox
diff --git a/libs/remix-ui/checkbox/tsconfig.json b/libs/remix-ui/checkbox/tsconfig.json
new file mode 100644
index 0000000000..6b65264565
--- /dev/null
+++ b/libs/remix-ui/checkbox/tsconfig.json
@@ -0,0 +1,16 @@
+{
+ "extends": "../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "react",
+ "allowJs": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true
+ },
+ "files": [],
+ "include": [],
+ "references": [
+ {
+ "path": "./tsconfig.lib.json"
+ }
+ ]
+}
diff --git a/libs/remix-ui/checkbox/tsconfig.lib.json b/libs/remix-ui/checkbox/tsconfig.lib.json
new file mode 100644
index 0000000000..b560bc4dec
--- /dev/null
+++ b/libs/remix-ui/checkbox/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"]
+}
diff --git a/libs/remix-ui/static-analyser/.babelrc b/libs/remix-ui/static-analyser/.babelrc
new file mode 100644
index 0000000000..09d67939cc
--- /dev/null
+++ b/libs/remix-ui/static-analyser/.babelrc
@@ -0,0 +1,4 @@
+{
+ "presets": ["@nrwl/react/babel"],
+ "plugins": []
+}
diff --git a/libs/remix-ui/static-analyser/.eslintrc b/libs/remix-ui/static-analyser/.eslintrc
new file mode 100644
index 0000000000..dae5c6feeb
--- /dev/null
+++ b/libs/remix-ui/static-analyser/.eslintrc
@@ -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"
+ }
+}
diff --git a/libs/remix-ui/static-analyser/README.md b/libs/remix-ui/static-analyser/README.md
new file mode 100644
index 0000000000..7e4b95c0e5
--- /dev/null
+++ b/libs/remix-ui/static-analyser/README.md
@@ -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).
diff --git a/libs/remix-ui/static-analyser/src/index.ts b/libs/remix-ui/static-analyser/src/index.ts
new file mode 100644
index 0000000000..86a00ccd14
--- /dev/null
+++ b/libs/remix-ui/static-analyser/src/index.ts
@@ -0,0 +1 @@
+export * from './lib/remix-ui-static-analyser'
diff --git a/libs/remix-ui/static-analyser/src/lib/Button/StaticAnalyserButton.tsx b/libs/remix-ui/static-analyser/src/lib/Button/StaticAnalyserButton.tsx
new file mode 100644
index 0000000000..2a67c82cf8
--- /dev/null
+++ b/libs/remix-ui/static-analyser/src/lib/Button/StaticAnalyserButton.tsx
@@ -0,0 +1,21 @@
+import React from 'react' //eslint-disable-line
+
+interface StaticAnalyserButtonProps {
+ onClick: (event) => void
+ buttonText: string,
+ disabled?: boolean
+}
+
+const StaticAnalyserButton = ({
+ onClick,
+ buttonText,
+ disabled
+}: StaticAnalyserButtonProps) => {
+ return (
+
+ )
+}
+
+export default StaticAnalyserButton
diff --git a/libs/remix-ui/static-analyser/src/lib/ErrorRenderer.tsx b/libs/remix-ui/static-analyser/src/lib/ErrorRenderer.tsx
new file mode 100644
index 0000000000..8221d3f694
--- /dev/null
+++ b/libs/remix-ui/static-analyser/src/lib/ErrorRenderer.tsx
@@ -0,0 +1,65 @@
+import React from 'react' //eslint-disable-line
+
+interface ErrorRendererProps {
+ message: any;
+ opt: any,
+ warningErrors: any
+ editor: any
+}
+
+const ErrorRenderer = ({ message, opt, editor }: ErrorRendererProps) => {
+ const getPositionDetails = (msg: any) => {
+ const result = { } as Record
+
+ // 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
+ }
+
+ const handlePointToErrorOnClick = (location, fileName) => {
+ editor.call('editor', 'discardHighlight')
+ editor.call('editor', 'highlight', location, fileName)
+ }
+
+ 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()
+ const classList = opt.type === 'error' ? 'alert alert-danger' : 'alert alert-warning'
+ return (
+
+
+
+
+
+
handlePointToErrorOnClick(opt.location, opt.fileName)}>
+ {opt.name}
+ { opt.item.warning }
+ {opt.item.more
+ ? more
+ :
+ }
+ Pos: {opt.locationString}
+
+
+
+ )
+}
+
+export default ErrorRenderer
diff --git a/libs/remix-ui/static-analyser/src/lib/actions/staticAnalysisActions.ts b/libs/remix-ui/static-analyser/src/lib/actions/staticAnalysisActions.ts
new file mode 100644
index 0000000000..4f55437cb6
--- /dev/null
+++ b/libs/remix-ui/static-analyser/src/lib/actions/staticAnalysisActions.ts
@@ -0,0 +1,14 @@
+import React from 'react' //eslint-disable-line
+
+export const compilation = (analysisModule, dispatch) => {
+ if (analysisModule) {
+ analysisModule.on(
+ 'solidity',
+ 'compilationFinished',
+ (file, source, languageVersion, data) => {
+ if (languageVersion.indexOf('soljson') !== 0) return
+ dispatch({ type: 'compilationFinished', payload: { file, source, languageVersion, data } })
+ }
+ )
+ }
+}
diff --git a/libs/remix-ui/static-analyser/src/lib/reducers/staticAnalysisReducer.ts b/libs/remix-ui/static-analyser/src/lib/reducers/staticAnalysisReducer.ts
new file mode 100644
index 0000000000..eb59ca7872
--- /dev/null
+++ b/libs/remix-ui/static-analyser/src/lib/reducers/staticAnalysisReducer.ts
@@ -0,0 +1,21 @@
+export const initialState = {
+ file: null,
+ source: null,
+ languageVersion: null,
+ data: null
+}
+
+export const analysisReducer = (state, action) => {
+ switch (action.type) {
+ case 'compilationFinished':
+ return {
+ ...state,
+ file: action.payload.file,
+ source: action.payload.source,
+ languageVersion: action.payload.languageVersion,
+ data: action.payload.data
+ }
+ default:
+ return initialState
+ }
+}
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
new file mode 100644
index 0000000000..5bb91441af
--- /dev/null
+++ b/libs/remix-ui/static-analyser/src/lib/remix-ui-static-analyser.tsx
@@ -0,0 +1,351 @@
+import React, { useEffect, useState, useReducer } from 'react'
+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 { 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'
+const StaticAnalysisRunner = require('@remix-project/remix-analyzer').CodeAnalysis
+const utils = remixLib.util
+
+/* eslint-disable-next-line */
+export interface RemixUiStaticAnalyserProps {
+ registry: any,
+ event: any,
+ analysisModule: 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 [autoRun, setAutoRun] = useState(true)
+ const [categoryIndex, setCategoryIndex] = useState(groupedModuleIndex(groupedModules))
+
+ const warningContainer = React.useRef(null)
+ const [warningState, setWarningState] = useState([])
+ const [state, dispatch] = useReducer(analysisReducer, initialState)
+
+ useEffect(() => {
+ compilation(props.analysisModule, dispatch)
+ }, [])
+
+ useEffect(() => {
+ if (autoRun) {
+ if (state.data !== null) {
+ run(state.data, state.source, state.file)
+ }
+ }
+ return () => { }
+ }, [autoRun, categoryIndex, state])
+
+ const message = (name, warning, more, fileName, locationString) : string => {
+ return (`
+
+ ${name}
+ ${warning}
+ ${more
+ ? (more)
+ : ( )
+ }
+ Pos: ${locationString}
+ `
+ )
+ }
+
+ const run = (lastCompilationResult, lastCompilationSource, currentFile) => {
+ if (state.data !== null) {
+ if (lastCompilationResult && categoryIndex.length > 0) {
+ 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
+ }
+ })
+ })
+ 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) {
+ const split = item.location.split(':')
+ const 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 = message(item.name, item.warning, item.more, fileName, locationString)
+ const options = {
+ type: 'warning',
+ useSpan: true,
+ errFile: fileName,
+ fileName,
+ errLine: row,
+ errCol: column,
+ item: item,
+ name: result.name,
+ locationString,
+ more: item.more,
+ location: location
+ }
+ warningErrors.push(options)
+ 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)
+ })
+ if (categoryIndex.length > 0) {
+ props.event.trigger('staticAnaysisWarning', [warningCount])
+ }
+ } else {
+ if (categoryIndex.length) {
+ warningContainer.current.innerText = 'No compiled AST available'
+ }
+ props.event.trigger('staticAnaysisWarning', [-1])
+ }
+ }
+ }
+
+ 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)
+ }
+ }
+
+ 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 (
+
+ handleCheckSingle(event, item._index)}
+ checked={categoryIndex.includes(item._index.toString())}
+ onChange={() => {}}
+ />
+
+ )
+ }
+
+ const categorySection = (category, categoryId, i) => {
+ return (
+
+
+
+
+ {category[0].categoryDisplayName}
+
+ }
+ expand={false}
+ >
+
+ 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))} onChange={() => {}}/>
+
+
+ {category.map((item, i) => {
+ return (
+ categoryItem(categoryId, item, i)
+ )
+ })}
+
+
+
+
+
+ )
+ }
+
+ return (
+
+
+
+ {
+ return (value.map(x => {
+ return x._index.toString()
+ }))
+ }).flat().every(el => categoryIndex.includes(el))}
+ label="Select all"
+ onClick={() => handleCheckAllModules(groupedModules)}
+ onChange={() => {}}
+ />
+ {}}
+ />
+
+
+
+ {Object.keys(groupedModules).map((categoryId, i) => {
+ const category = groupedModules[categoryId]
+ return (
+ categorySection(category, categoryId, i)
+ )
+ })
+ }
+
+
+ last results for:
+
+ {state.file}
+
+
+ { categoryIndex.length > 0 && Object.entries(warningState).length > 0 &&
+
+
+ {
+ (Object.entries(warningState).map((element) => (
+ <>
+
{element[0]}
+ {element[1].map(x => (
+ x.hasWarning ? (
+
+
+
+
+ ) : null
+ ))}
+ >
+ )))
+ }
+
+
+ }
+
+ )
+}
+
+export default RemixUiStaticAnalyser
diff --git a/libs/remix-ui/static-analyser/tsconfig.json b/libs/remix-ui/static-analyser/tsconfig.json
new file mode 100644
index 0000000000..6b65264565
--- /dev/null
+++ b/libs/remix-ui/static-analyser/tsconfig.json
@@ -0,0 +1,16 @@
+{
+ "extends": "../../../tsconfig.json",
+ "compilerOptions": {
+ "jsx": "react",
+ "allowJs": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true
+ },
+ "files": [],
+ "include": [],
+ "references": [
+ {
+ "path": "./tsconfig.lib.json"
+ }
+ ]
+}
diff --git a/libs/remix-ui/static-analyser/tsconfig.lib.json b/libs/remix-ui/static-analyser/tsconfig.lib.json
new file mode 100644
index 0000000000..b560bc4dec
--- /dev/null
+++ b/libs/remix-ui/static-analyser/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"]
+}
diff --git a/nx.json b/nx.json
index 5cf047d9cc..a63c015d64 100644
--- a/nx.json
+++ b/nx.json
@@ -95,6 +95,12 @@
},
"remix-ui-workspace": {
"tags": []
+ },
+ "remix-ui-static-analyser": {
+ "tags": []
+ },
+ "remix-ui-checkbox": {
+ "tags": []
}
}
}
diff --git a/package.json b/package.json
index a7ae4d3288..0e3b262c5e 100644
--- a/package.json
+++ b/package.json
@@ -41,7 +41,7 @@
"workspace-schematic": "nx workspace-schematic",
"dep-graph": "nx dep-graph",
"help": "nx help",
- "lint:libs": "nx run-many --target=lint --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd,remix-ui-tree-view,remix-ui-modal-dialog,remix-ui-toaster,remix-ui-file-explorer,remix-ui-debugger-ui,remix-ui-workspace",
+ "lint:libs": "nx run-many --target=lint --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd,remix-ui-tree-view,remix-ui-modal-dialog,remix-ui-toaster,remix-ui-file-explorer,remix-ui-debugger-ui,remix-ui-workspace,remix-ui-static-analyser,remix-ui-checkbox",
"build:libs": "nx run-many --target=build --parallel=false --with-deps=true --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd",
"test:libs": "nx run-many --target=test --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd",
"publish:libs": "npm run build:libs & lerna publish --skip-git & npm run bumpVersion:libs",
@@ -159,6 +159,7 @@
"isbinaryfile": "^3.0.2",
"jquery": "^3.3.1",
"jszip": "^3.6.0",
+ "lodash": "^4.17.21",
"latest-version": "^5.1.0",
"lodash": "^4.17.21",
"merge": "^1.2.0",
diff --git a/tsconfig.json b/tsconfig.json
index c2a383e9de..75db5bc6ff 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -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,9 @@
"@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"],
+ "@remix-ui/checkbox": ["libs/remix-ui/checkbox/src/index.ts"]
}
},
"exclude": ["node_modules", "tmp"]
diff --git a/workspace.json b/workspace.json
index 10793e13b3..5d2bf912c0 100644
--- a/workspace.json
+++ b/workspace.json
@@ -725,6 +725,41 @@
}
}
}
+ },
+ "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/**/*"
+ ]
+ }
+ }
+ }
+ },
+ "remix-ui-checkbox": {
+ "root": "libs/remix-ui/checkbox",
+ "sourceRoot": "libs/remix-ui/checkbox/src",
+ "projectType": "library",
+ "schematics": {},
+ "architect": {
+ "lint": {
+ "builder": "@nrwl/linter:lint",
+ "options": {
+ "linter": "eslint",
+ "tsConfig": ["libs/remix-ui/checkbox/tsconfig.lib.json"],
+ "exclude": ["**/node_modules/**", "!libs/remix-ui/checkbox/**/*"]
+ }
+ }
+ }
}
},
"cli": {