diff --git a/CHANGELOG.md b/CHANGELOG.md index 98d7354..568ac7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,15 +2,23 @@ All notable changes to the "solidity-visual-auditor" extension will be documented in this file. ## v0.0.23 -- new: Update notifications! - can be disabled of course :) +- new: Update notifications have arrived! +- updated: solidity parser and surya +- new: 🔥 Solidity Visual Auditor Cockpit panel + - Workspace Explorer + - Quick-access to extension settings + - Find Top Level Contracts + - Keep track of flattened files + - List public state-changing methods from the current contract + - Show the function call trace for the current method ## v0.0.22 -- update: solidity parser, surya (#41 #42) +- update: solidity parser, surya (#41 [#42](https://github.com/tintinweb/vscode-solidity-auditor/issues/42)) - fix: linter warnings (#40) - fix: configuration changes now take effect immediately (#43) ## v0.0.21 -- fix: Support VSCode for Windows (#38, #35) +- fix: Support VSCode for Windows (#38, [#35](https://github.com/tintinweb/vscode-solidity-auditor/issues/35)) - fix: UML arrows (#34) - code cleanup (#39) - allow extension to run on unsaved files/editors (some functionality will not work on unsaved files, e.g. `surya` calls) @@ -40,7 +48,7 @@ All notable changes to the "solidity-visual-auditor" extension will be documente sva_light_vscode - new: codelense next to functions to generate sighash. -- fix: function signature hashes are now generated for all functions (even internal ones, just ignore them for now :)). Canonicalization of types before calculating hashes #27. +- fix: function signature hashes are now generated for all functions (even internal ones, just ignore them for now :)). Canonicalization of types before calculating hashes [#27](https://github.com/tintinweb/vscode-solidity-auditor/issues/27). - new: alert on function sighash collision within the same contract. sva_light_vscode diff --git a/README.md b/README.md index f1673ce..c18a0f9 100644 --- a/README.md +++ b/README.md @@ -285,7 +285,15 @@ This feature is provided by [Inline Bookmarks](https://marketplace.visualstudio. # Release Notes ## v0.0.23 -- new: Update notifications! - can be disabled of course :) +- new: Update notifications have arrived! +- updated: solidity parser and surya +- new: 🔥 Solidity Visual Auditor Cockpit panel + - Workspace Explorer + - Quick-access to extension settings + - Find Top Level Contracts + - Keep track of flattened files + - List public state-changing methods from the current contract + - Show the function call trace for the current method [Changelog](https://github.com/tintinweb/vscode-solidity-auditor/blob/master/CHANGELOG.md) diff --git a/images/cmd-flatten.svg b/images/cmd-flatten.svg new file mode 100644 index 0000000..46a24ab --- /dev/null +++ b/images/cmd-flatten.svg @@ -0,0 +1,116 @@ + + + + + + + + + + + + + image/svg+xml + + + + + Openclipart + + + + + + + + + + + diff --git a/images/glass3.png b/images/glass3.png new file mode 100644 index 0000000..8df5097 Binary files /dev/null and b/images/glass3.png differ diff --git a/images/panel-icon.svg b/images/panel-icon.svg new file mode 100644 index 0000000..025abb2 --- /dev/null +++ b/images/panel-icon.svg @@ -0,0 +1,3793 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/images/refresh-dark.svg b/images/refresh-dark.svg new file mode 100644 index 0000000..2e184ea --- /dev/null +++ b/images/refresh-dark.svg @@ -0,0 +1 @@ + diff --git a/images/refresh-light.svg b/images/refresh-light.svg new file mode 100644 index 0000000..a88fab8 --- /dev/null +++ b/images/refresh-light.svg @@ -0,0 +1 @@ + diff --git a/package.json b/package.json index ce20365..266b201 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "compiler", "security" ], - "version": "0.0.22", + "version": "0.0.23", "publisher": "tintinweb", "icon": "images/icon.png", "engines": { @@ -27,7 +27,26 @@ }, "activationEvents": [ "onLanguage:solidity", - "onCommand:solidity-va.whatsNew.show" + "onCommand:solidity-va.whatsNew.show", + "onCommand:solidity-va.surya.graph", + "onCommand:solidity-va.surya.inheritance", + "onCommand:solidity-va.insights.topLevelContracts", + "onCommand:solidity-va.tools.flaterra", + "onCommand:solidity-va.cockpit.explorer.context.flatten", + "onCommand:solidity-va.tools.flattenCandidates", + "onCommand:solidity-va.tools.function.signatures", + "onCommand:solidity-va.tools.function.signatures.json", + "onCommand:solidity-va.tools.function.signatures.forWorkspace.json", + "onCommand:solidity-va.tools.remix.openExternal", + "onCommand:solidity-va.cockpit.explorer.refresh", + "onCommand:solidity-va.cockpit.topLevelContracts.refresh", + "onCommand:solidity-va.cockpit.settings.toggle", + "onView:solidity-va-cockpit-explorer", + "onView:solidity-va-cockpit-topLevelContracts", + "onView:solidity-va-cockpit-settings", + "onView:solidity-va-cockpit-ftrace", + "onView:solidity-va-cockpit-publicMethods", + "onView:solidity-va-cockpit" ], "main": "./src/extension", "capabilities": { @@ -74,6 +93,96 @@ "path": "./src/themes/light_vs.json" } ], + "viewsContainers": { + "activitybar": [ + { + "id": "solidity-va-cockpit", + "title": "Solidity Visual Auditor", + "icon": "images/glass3.png" + } + ] + }, + "views": { + "solidity-va-cockpit": [ + { + "id": "solidity-va-cockpit-explorer", + "name": "Workspace: Explorer" + }, + { + "id": "solidity-va-cockpit-topLevelContracts", + "name": "Workspace: Top Level Contracts" + }, + { + "id": "solidity-va-cockpit-flatFiles", + "name": "Workspace: */flat_* Files", + "when": "ONLY_BETA" + }, + { + "id": "solidity-va-cockpit-publicMethods", + "name": "Context: Public StateChanging Methods" + }, + { + "id": "solidity-va-cockpit-ftrace", + "name": "Context: Function Call Trace", + "when": "ONLY_BETA" + }, + { + "id": "solidity-va-cockpit-settings", + "name": "Settings" + } + ] + }, + "menus": { + "view/title": [ + { + "command": "solidity-va.cockpit.explorer.refresh", + "when": "view == solidity-va-cockpit-explorer", + "group": "navigation" + }, + { + "command": "solidity-va.cockpit.topLevelContracts.refresh", + "when": "view == solidity-va-cockpit-topLevelContracts", + "group": "navigation" + }, + { + "command": "solidity-va.cockpit.topLevelContracts.flatten", + "when": "view == solidity-va-cockpit-topLevelContracts", + "group": "navigation" + }, + { + "command": "solidity-va.cockpit.flatFiles.refresh", + "when": "view == solidity-va-cockpit-flatFiles", + "group": "navigation" + } + ], + "view/item/context": [ + { + "group": "solidity", + "command": "solidity-va.cockpit.topLevelContracts.refresh", + "when": "view == solidity-va-cockpit-explorer && viewItem =~ /^((?!\\.sol).)*$/" + }, + { + "group": "solidity", + "command": "solidity-va.cockpit.explorer.context.flatten", + "when": "view == solidity-va-cockpit-explorer && viewItem =~ /\\.sol/ || view == solidity-va-cockpit-topLevelContracts" + }, + { + "group": "solidity", + "command": "solidity-va.surya.mdreport", + "when": "view == solidity-va-cockpit-explorer || view == solidity-va-cockpit-topLevelContracts || view == solidity-va-cockpit-flatFiles" + }, + { + "group": "solidity", + "command": "solidity-va.surya.graph", + "when": "view == solidity-va-cockpit-explorer || view == solidity-va-cockpit-topLevelContracts || view == solidity-va-cockpit-flatFiles" + }, + { + "group": "solidity", + "command": "solidity-va.surya.inheritance", + "when": "view == solidity-va-cockpit-explorer || view == solidity-va-cockpit-topLevelContracts || view == solidity-va-cockpit-flatFiles" + } + ] + }, "commands": [ { "command": "solidity-va.test.createTemplate", @@ -110,6 +219,20 @@ "title": "Tools - flatten current file", "category": "Solidity Visual Auditor" }, + { + "command": "solidity-va.cockpit.explorer.context.flatten", + "title": "Solidity - Flatten Selected File(s)", + "category": "Solidity Visual Auditor" + }, + { + "command": "solidity-va.cockpit.topLevelContracts.flatten", + "title": "Flatten", + "category": "Solidity Visual Auditor", + "icon": { + "light": "images/cmd-flatten.svg", + "dark": "images/cmd-flatten.svg" + } + }, { "command": "solidity-va.tools.flattenCandidates", "title": "Tools - flatten all suggested top level contracts", @@ -135,6 +258,38 @@ "title": "Tools - launch Remix-IDE", "category": "Solidity Visual Auditor" }, + { + "command": "solidity-va.cockpit.explorer.refresh", + "title": "Scan Workspace", + "category": "Solidity Visual Auditor", + "icon": { + "light": "images/refresh-light.svg", + "dark": "images/refresh-dark.svg" + } + }, + { + "command": "solidity-va.cockpit.topLevelContracts.refresh", + "title": "Find Top Level Contracts", + "category": "Solidity Visual Auditor", + "icon": { + "light": "images/refresh-light.svg", + "dark": "images/refresh-dark.svg" + } + }, + { + "command": "solidity-va.cockpit.flatFiles.refresh", + "title": "Scan Workspace", + "category": "Solidity Visual Auditor", + "icon": { + "light": "images/refresh-light.svg", + "dark": "images/refresh-dark.svg" + } + }, + { + "command": "solidity-va.cockpit.settings.toggle", + "title": "Toggle Setting", + "category": "Solidity Visual Auditor" + }, { "command": "solidity-va.whatsNew.show", "title": "Extension - What's New", @@ -249,6 +404,15 @@ "default": false, "description": "Enable/Disable actors in uml" }, + "solidity-va.cockpit.view.topLevelContracts.listStyle": { + "type": "string", + "enum": [ + "flat", + "tree" + ], + "default": "flat", + "description": "Select TopLevelContracts view list style." + }, "solidity-va.whatsNew.disabled": { "type": "boolean", "default": false, diff --git a/src/extension.js b/src/extension.js index 0202cd5..c0a6776 100644 --- a/src/extension.js +++ b/src/extension.js @@ -8,6 +8,7 @@ /** imports */ const vscode = require('vscode'); const {CancellationTokenSource} = require('vscode'); +const path = require('path'); //const mod_codelens = require('./features/codelens'); const mod_hover = require('./features/hover'); @@ -19,6 +20,7 @@ const {DiliDiagnosticCollection} = require('./features/genericDiag'); const {Commands} = require('./features/commands'); const {SolidityCodeLensProvider} = require('./features/codelens'); const settings = require('./settings'); +const {Cockpit} = require('./features/cockpit.js'); const {WhatsNewHandler} = require('./features/whatsnew/whatsNew'); @@ -47,6 +49,17 @@ const ScopeEnum = { /** helper */ +function editorJumptoRange(editor, range) { + let revealType = vscode.TextEditorRevealType.InCenter; + let selection = new vscode.Selection(range.start.line, range.start.character, range.end.line, range.end.character); + if (range.start.line === editor.selection.active.line) { + revealType = vscode.TextEditorRevealType.InCenterIfOutsideViewport; + } + + editor.selection = selection; + editor.revealRange(selection, revealType); +} + async function setDecorations(editor, decorations){ if (!editor) { return; @@ -611,6 +624,7 @@ function onActivate(context) { onDidChange(); let commands = new Commands(g_parser); + let cockpit = new Cockpit(commands); /** command setup */ context.subscriptions.push( @@ -633,7 +647,8 @@ function onActivate(context) { context.subscriptions.push( vscode.commands.registerCommand( 'solidity-va.surya.mdreport', - function (doc) { + function (doc, multiSelectTreeItems) { + doc = multiSelectTreeItems || doc; commands.surya(doc || vscode.window.activeTextEditor.document, "mdreport"); } ) @@ -643,6 +658,11 @@ function onActivate(context) { vscode.commands.registerCommand( 'solidity-va.surya.graph', function (doc, files) { + if(files && typeof files[0] === "object" && files[0].hasOwnProperty("children")){ + //treeItem or fspaths + doc = files; + files = undefined; + } commands.surya(doc || vscode.window.activeTextEditor.document, "graph", files); } ) @@ -650,7 +670,8 @@ function onActivate(context) { context.subscriptions.push( vscode.commands.registerCommand( 'solidity-va.surya.inheritance', - function (doc) { + function (doc, multiSelectTreeItems) { + doc = multiSelectTreeItems || doc; commands.surya(doc || vscode.window.activeTextEditor.document, "inheritance"); } ) @@ -697,6 +718,27 @@ function onActivate(context) { } ) ); + + context.subscriptions.push( + vscode.commands.registerCommand( + 'solidity-va.cockpit.explorer.context.flatten', + async function (treeItem, multiSelectTreeItems) { + multiSelectTreeItems = multiSelectTreeItems || []; + [...multiSelectTreeItems, treeItem].forEach(async treeItem => { + await vscode.extensions + .getExtension('tintinweb.vscode-solidity-flattener') + .activate() + .then( + async () => { + vscode.commands + .executeCommand('vscode-solidity-flattener.contextMenu.flatten', [], [treeItem.resource]) + .then(async (done) => {}); + }); + }); + } + ) + ); + context.subscriptions.push( vscode.commands.registerCommand( 'solidity-va.tools.flattenCandidates', @@ -706,6 +748,20 @@ function onActivate(context) { ) ); + context.subscriptions.push( + vscode.commands.registerCommand( + 'solidity-va.cockpit.topLevelContracts.flatten', + function () { + let sourceFiles = cockpit.views.topLevelContracts.dataProvider.data.reduce(function (obj, item) { + obj[path.basename(item.path,".sol")] = vscode.Uri.file(item.path); + return obj; + }, {}); + commands.flattenCandidates(sourceFiles); + cockpit.views.flatFiles.refresh(); + } + ) + ); + context.subscriptions.push( vscode.commands.registerCommand( 'solidity-va.tools.function.signatures', @@ -760,6 +816,49 @@ function onActivate(context) { ) ); + context.subscriptions.push( + vscode.commands.registerCommand("solidity-va.cockpit.topLevelContracts.refresh", async (treeItem, multiSelectTreeItems) => { + if(multiSelectTreeItems){ + cockpit.views.topLevelContracts.refresh(multiSelectTreeItems.filter(t => !t.path.endsWith(".sol")).map(t => t.path)); + } else { + cockpit.views.topLevelContracts.refresh(treeItem && treeItem.path); + } + }) + ); + + context.subscriptions.push( + vscode.commands.registerCommand("solidity-va.cockpit.explorer.refresh", async () => { + cockpit.views.explorer.refresh(); + }) + ); + + context.subscriptions.push( + vscode.commands.registerCommand("solidity-va.cockpit.flatFiles.refresh", async () => { + cockpit.views.flatFiles.refresh(); + }) + ); + + context.subscriptions.push( + vscode.commands.registerCommand("solidity-va.cockpit.jumpToRange", (documentUri, range) => { + vscode.workspace.openTextDocument(documentUri).then(doc => { + vscode.window.showTextDocument(doc).then(editor => { + if(range) { + editorJumptoRange(editor, range); + } + }); + }); + }) + ); + + context.subscriptions.push( + vscode.commands.registerCommand("solidity-va.cockpit.settings.toggle", async (treeItem) => { + let cfg = vscode.workspace.getConfiguration(treeItem.metadata.extension); + let current = cfg.get(treeItem.metadata.section); + await cfg.update(treeItem.metadata.section, !current); + cockpit.views.settings.refresh(); + }) + ); + /** event setup */ /***** DidChange */ vscode.window.onDidChangeActiveTextEditor(editor => { @@ -784,6 +883,11 @@ function onActivate(context) { onDidSave(document); }, null, context.subscriptions); + /****** onDidChangeTextEditorSelection */ + vscode.window.onDidChangeTextEditorSelection(event /* TextEditorVisibleRangesChangeEvent */ => { + cockpit.onDidSelectionChange(event); // let cockpit handle the event + }, null, context.subscriptions); + context.subscriptions.push( vscode.languages.registerHoverProvider(type, { diff --git a/src/features/cockpit.js b/src/features/cockpit.js new file mode 100644 index 0000000..f8c988e --- /dev/null +++ b/src/features/cockpit.js @@ -0,0 +1,826 @@ +'use strict'; +/** + * @author github.com/tintinweb + * @license MIT + * + * + * */ +const vscode = require('vscode'); +const settings = require('../settings.js'); +const surya = require('surya'); +const path = require('path'); +const fs = require('fs'); + +/** views */ + +class BaseView { + async refresh(value){ + this.treeView.message = undefined; // clear the treeview message + return this.dataProvider.refresh(value); + } + async onDidSelectionChange(event) {} +} + +class BaseDataProvider { + async dataGetRoot(){ + return []; + } + + dataGetChildren(element){ + return null; + } + + /** tree methods */ + getChildren(element){ + return element ? this.dataGetChildren(element): this.dataGetRoot(); + } + + getParent(element){ + return element.parent; + } + + getTreeItem(element){ + return { + resourceUri: element.resource, + label: element.label, + iconPath: element.iconPath, + collapsibleState: element.collapsibleState, + children: element.children, + command: element.command || { + command: 'solidity-va.cockpit.jumpToRange', + arguments: [element.resource], + title: 'JumpTo' + } + }; + } + + /** other methods */ + refresh(){ + return new Promise((resolve, reject) => { + this._onDidChangeTreeData.fire(); + resolve(); + }); + } +} + +/** Generic Data Provider */ +/* helper */ + +class FilePathTreeDataProvider extends BaseDataProvider { + constructor(listStyle, separator) { + super(); + this.listStyle = listStyle; + this._separator = separator || path.sep; + this.data = []; + } + + async dataGetRoot(){ + return this.data; + } + + dataGetChildren(element) { + if(!element) { + return this.data; + } + // element provided? - + return element.children; + } + + dataGetParent(element){ + return element.parent; + } + + _addPathTree(uri){ + //strip workspace path + let workspacePath = vscode.workspace.getWorkspaceFolder(uri).uri.fsPath; + let pathSegments = path.relative(workspacePath, uri.fsPath).split(this._separator); + let parent = this.data; + + for(let idx = 0; idx < pathSegments.length; idx++){ + let name = pathSegments[idx]; + if(name == ""){ + continue; + } + let pathObj = parent.find( p => p.name == name); + + if(!pathObj){ + //create a new one + let _path = pathSegments.slice(0, idx+1).join(this._separator); + let _abspath = path.join(workspacePath,_path); + let _type = FilePathTreeDataProvider.TYPE_FILE; + try { + _type = fs.lstatSync(_abspath).isDirectory() ? FilePathTreeDataProvider.TYPE_DIRECTORY : FilePathTreeDataProvider.TYPE_FILE; + } catch (err) { + console.warn(err); //fallback to type file + } + + pathObj = { + name: name, + path: _path, + resource: vscode.Uri.file(_abspath), + children: [], + parent: parent, + type: _type, + workspace: workspacePath, + collapsibleState: _type === FilePathTreeDataProvider.TYPE_DIRECTORY ? vscode.TreeItemCollapsibleState.Collapsed : 0, + }; + parent.push(pathObj); + } + parent = pathObj.children; + } + } + + _addPathFlat(uri){ + let pathSegments = uri.fsPath.split(this._separator); + let workspacePath = vscode.workspace.getWorkspaceFolder(uri).uri.fsPath; + this.data.push( + { + name: pathSegments[pathSegments.length - 1], + path: uri.fsPath, + resource: uri, + children: [], + parent: null, + type: FilePathTreeDataProvider.TYPE_FILE, + workspace: workspacePath, + collapsibleState: 0, + } + ); + } + + addPath(uri){ + if(uri.scheme === undefined){ + uri = vscode.Uri.file(uri); + } + if(this.listStyle === "flat"){ + this._addPathFlat(uri); + } else { + this._addPathTree(uri); + } + } + + load(paths) { + this.data = []; + for(let p of paths){ + this.addPath(p); + } + } +} +FilePathTreeDataProvider.TYPE_DIRECTORY = 1; +FilePathTreeDataProvider.TYPE_FILE = 2; + +class VirtualPathTreeDataProvider extends FilePathTreeDataProvider { + + _addPathTree(s, metadata){ + //strip workspace path + let pathSegments = s.split(this._separator); + let parent = this.data; + + for(let idx = 0; idx < pathSegments.length; idx++){ + let name = pathSegments[idx]; + if(name == ""){ + continue; + } + var pathObj = parent.find( p => p.name == name); + + if(!pathObj){ + //create a new one + let _path = pathSegments.slice(0, idx+1).join(this._separator); + let _type = idx == pathSegments.length -1 ? VirtualPathTreeDataProvider.TYPE_LEAF : VirtualPathTreeDataProvider.TYPE_NODE; + pathObj = { + name: name, + path: _path, + label: name, + metadata: metadata, + resource: null, + children: [], + parent: parent, + type: _type, + collapsibleState: _type == VirtualPathTreeDataProvider.TYPE_LEAF ? 0 : vscode.TreeItemCollapsibleState.Collapsed, + }; + parent.push(pathObj); + } + parent = pathObj.children; + } + } + + _addPathFlat(s, metadata){ + let pathSegments = s.split(this._separator); + this.data.push( + { + name: pathSegments[pathSegments.length - 1], + path: s, + label: s, + metadata: metadata, + resource: null, + children: [], + parent: null, + type: VirtualPathTreeDataProvider.TYPE_LEAF, + collapsibleState: 0, + } + ); + } + + addPath(s, metadata){ + if(this.listStyle === "flat"){ + this._addPathFlat(s, metadata); + } else { + this._addPathTree(s, metadata); + } + } + + load(paths) { + this.data = []; + + if(Array.isArray(paths)){ + for(let p of paths){ + this.addPath(p); + } + } else { + for(let p of Object.keys(paths)){ + this.addPath(p, paths[p]); + } + } + + } +} +VirtualPathTreeDataProvider.TYPE_NODE = 1; +VirtualPathTreeDataProvider.TYPE_LEAF = 2; + +/* TopLevelContracts View */ + +class TopLevelContractsViewDataProvider extends FilePathTreeDataProvider { + + constructor(treeView){ + super(settings.extensionConfig().cockpit.view.topLevelContracts.listStyle); + this.treeView = treeView; + this._onDidChangeTreeData = new vscode.EventEmitter(); + this.onDidChangeTreeData = this._onDidChangeTreeData.event; + + this.data = null; + } + + async dataGetRoot(){ + return this.data || []; + } + + /** events */ + + /** tree methods */ + // inherited. + + getTreeItem(element){ + let ret = { + resourceUri: element.resource, + label: element.label, + iconPath: element.iconPath, + collapsibleState: element.collapsibleState, + command: element.type === FilePathTreeDataProvider.TYPE_FILE ? { + command: 'solidity-va.cockpit.jumpToRange', + arguments: [element.resource], + title: 'JumpTo' + } : 0, + }; + return ret; + } + + /** other methods */ + refresh(workspaceRelativeBaseDir){ + return new Promise((resolve, reject) => { + this.treeView.cockpit.commands._findTopLevelContracts(undefined, undefined, workspaceRelativeBaseDir).then(data => { + this.load(Object.values(data)); + this._onDidChangeTreeData.fire(); + resolve(); + }); + }); + } +} + + +class DEPRECATED__TopLevelContractsViewDataProviderx extends BaseDataProvider { + + constructor(treeView){ + super(); + this.treeView = treeView; + this._onDidChangeTreeData = new vscode.EventEmitter(); + this.onDidChangeTreeData = this._onDidChangeTreeData.event; + + this.data = null; + } + + async dataGetRoot(){ + if(this.data === null){ + return []; + await this.refresh(); //first time: get data + } + return Object.keys(this.data).map(k => { + return { + resource: this.data[k], //uri + tooltip: k, + name: k, + parent: null, + iconPath: vscode.ThemeIcon.File, + }; + }); + } + + dataGetChildren(){ + return null; //no children :) + } + + /** events */ + + /** tree methods */ + // inherited. + + /** other methods */ + refresh(){ + return new Promise((resolve, reject) => { + this.treeView.cockpit.commands._findTopLevelContracts().then(data => { + this.data = data; + this._onDidChangeTreeData.fire(); + resolve(); + }); + }); + } +} + + +class TopLevelContractsView extends BaseView { + constructor(cockpit) { + super(); + this.cockpit = cockpit; + this.id = "topLevelContracts"; + this.dataProvider = new TopLevelContractsViewDataProvider(this); + this.treeView = vscode.window.createTreeView(`solidity-va-cockpit-${this.id}`, { treeDataProvider:this.dataProvider }); + this.treeView.message = "click ↻ to scan for contracts..."; + } +} + +/* FTrace View */ + +class FTraceViewDataProvider extends BaseDataProvider { + + constructor(treeView){ + super(); + this.treeView = treeView; + this._onDidChangeTreeData = new vscode.EventEmitter(); + this.onDidChangeTreeData = this._onDidChangeTreeData.event; + + this.data = null; + this.documentUri = null; + } + + async dataGetRoot(){ + if(this.data === null || this.documentUri === null){ + return []; + } + return this.data.map(k => { + return { + resource: this.documentUri, //uri + label: k, + tooltip: k, + name: k, + parent: null, + iconPath: vscode.ThemeIcon.File, + }; + }); + } + + dataGetChildren(){ + return null; //no children :) + } + + /** events */ + + /** tree methods */ + // inherited. + +} + +class FTraceView extends BaseView { + constructor(cockpit) { + super(); + this.cockpit = cockpit; + this.id = "ftrace"; + this.dataProvider = new FTraceViewDataProvider(this); + this.treeView = vscode.window.createTreeView(`solidity-va-cockpit-${this.id}`, { treeDataProvider:this.dataProvider, showCollapseAll:true }); + this.treeView.message = "click into the editor to update view..."; + } + + async onDidSelectionChange(event){ + + let documentUri = event.textEditor._documentData._uri; + let focus = event.selections[0].anchor; + let commands = this.cockpit.commands; + + let contractObj = commands.g_parser.sourceUnits[documentUri.fsPath]; + let knownFiles = Object.keys(commands.g_parser.sourceUnits).filter(f => f.endsWith(".sol")); + + + if(!contractObj){ + console.warn("surya.ftrace: not a file: " + documentUri.fsPath); + return; + } + + let focusSolidityElement = contractObj.getFunctionAtLocation(focus.line, focus.character); + if(!focusSolidityElement){ + console.warn("surya.ftrace: contract not found: " + documentUri.fsPath); + return; + } + let contractName = focusSolidityElement.contract._node.name; + + if(!focusSolidityElement.function){ + return; + } + + let functionName = focusSolidityElement.function._node.name; + + + + let files; + if(settings.extensionConfig().tools.surya.input.contracts=="workspace"){ + await vscode.workspace.findFiles("**/*.sol", settings.DEFAULT_FINDFILES_EXCLUDES, 500) + .then(uris => { + files = uris.map(function (uri) { + return uri.fsPath; + }); + }); + } else { + files = [documentUri.fsPath, ...knownFiles]; //better only add imported files. need to resolve that somehow + } + + // contract::func, all, files + if (functionName === null){ + functionName = ""; + } else if (functionName === ""){ + functionName = ""; + } + + let ret = surya.ftrace(contractName + "::" + functionName, 'all', files, {}, true).trim(); + let lines = ret.match(/^.*((\r\n|\n|\r)|$)/gm); + this.dataProvider.documentUri = documentUri; + this.dataProvider.data = lines; + this.refresh(); + } +} + +/* Methods View */ + + +class PublicMethodsViewDataProvider extends FTraceViewDataProvider { + + async dataGetRoot(){ + if(this.data === null || this.documentUri === null){ + return []; + } + + return Object.keys(this.data) + .reduce((ret, key) => { + let element = this.data[key]; + let range = new vscode.Range(element._node.loc.start.line, element._node.loc.start.column, element._node.loc.end.line, element._node.loc.end.column); + let modifiers = Object.keys(element.modifiers); + let item = { + resource: element.resource, + contextValue: element.resource.fsPath, + range: range, + label: element._node.stateMutability == "payable" ? key + " 💰 " : key, + tooltip: key, + name: key, + iconPath: vscode.ThemeIcon.File, + collapsibleState: modifiers.length > 0 ? vscode.TreeItemCollapsibleState.Collapsed : 0, + parent: null, + children: modifiers.map(name => { + return { + //resource: element.resource, + label: "Ⓜ " + name, + //iconPath: 0, + command: { + command: 'solidity-va.cockpit.jumpToRange', + arguments: [element.resource, range], + title: 'JumpTo' + } + }; + }), + command: { + command: 'solidity-va.cockpit.jumpToRange', + arguments: [element.resource, range], + title: 'JumpTo' + }, + }; + ret.push(item); + return ret; + }, []); + } + + dataGetChildren(element){ + return element.children; + } + + /** events */ + + /** tree methods */ + // inherited. + +} + +class PublicMethodsView extends BaseView { + constructor(cockpit) { + super(); + this.cockpit = cockpit; + this.id = "publicMethods"; + this.dataProvider = new PublicMethodsViewDataProvider(this); + this.treeView = vscode.window.createTreeView(`solidity-va-cockpit-${this.id}`, { treeDataProvider:this.dataProvider }); + this.treeView.message = "click into the editor to update view..."; + } + + async onDidSelectionChange(event){ + + let documentUri = event.textEditor._documentData._uri; + let focus = event.selections[0].anchor; + let commands = this.cockpit.commands; + + let contractObj = commands.g_parser.sourceUnits[documentUri.fsPath]; + + + if(!contractObj){ + console.warn("cockpit.methods: not a file: " + documentUri.fsPath); + return; + } + + let focusSolidityElement = contractObj.getFunctionAtLocation(focus.line, focus.character); + if(!focusSolidityElement){ + console.warn("cockpit.methods: contract not found: " + documentUri.fsPath); + return; + } + + let filterNotVisibility = ["private","internal"]; + let filterNotStateMutability = ["view", "pure", "constant"]; + + let publicFunctions = Object.keys(focusSolidityElement.contract.functions) + .filter(f => { + let node = focusSolidityElement.contract.functions[f]._node; + //filter only for state changing public functions + return !filterNotVisibility.includes(node.visibility) && !filterNotStateMutability.includes(node.stateMutability); + }) + .reduce((obj, key) => { + let newKey = key; + let func = focusSolidityElement.contract.functions[key]; + + if (key === null || func._node.isConstructor){ + newKey = ""; + } else if (key === "" || func._node.isFallback){ + newKey = ""; + } + func.resource = documentUri; + obj[newKey] = func; + return obj; + }, {}); + // contract::func, all, files + this.dataProvider.documentUri = documentUri; + this.dataProvider.data = publicFunctions; + this.refresh(); + } +} + + +/* Solidity Files View */ + +class ExplorerViewDataProvider extends FilePathTreeDataProvider { + constructor(treeView){ + super("tree"); + this.treeView = treeView; + this._onDidChangeTreeData = new vscode.EventEmitter(); + this.onDidChangeTreeData = this._onDidChangeTreeData.event; + + this.data = null; + } + + async dataGetRoot(){ + if(this.data === null){ + this.refresh(); + } + return this.data || []; + } + + /** events */ + + /** tree methods */ + // inherited. + + getTreeItem(element){ + let ret = { + resourceUri: element.resource, + contextValue: element.resource.fsPath, + label: element.label, + iconPath: element.iconPath, + collapsibleState: element.collapsibleState, + command: element.type === FilePathTreeDataProvider.TYPE_FILE ? { + command: 'solidity-va.cockpit.jumpToRange', + arguments: [element.resource], + title: 'JumpTo' + } : 0, + }; + return ret; + } + + /** other methods */ + refresh(){ + return new Promise((resolve, reject) => { + vscode.workspace.findFiles("{**/*.sol}", settings.DEFAULT_FINDFILES_EXCLUDES_ALLOWFLAT, 5000) + .then((solfiles) => { + this.load(solfiles); + this._onDidChangeTreeData.fire(); + resolve(); + }); + }); + } +} + +class ExplorerView extends BaseView { + constructor(cockpit) { + super(); + this.cockpit = cockpit; + this.id = "explorer"; + this.dataProvider = new ExplorerViewDataProvider(this); + this.treeView = vscode.window.createTreeView(`solidity-va-cockpit-${this.id}`, { treeDataProvider:this.dataProvider, showCollapseAll:true, canSelectMany:true }); + } +} + +class FlatFilesDataProvider extends FilePathTreeDataProvider { + constructor(treeView){ + super("tree"); + this.treeView = treeView; + this._onDidChangeTreeData = new vscode.EventEmitter(); + this.onDidChangeTreeData = this._onDidChangeTreeData.event; + + this.data = null; + } + + async dataGetRoot(){ + if(this.data === null){ + this.refresh(); + } + return this.data || []; + } + + /** events */ + + /** tree methods */ + // inherited. + + getTreeItem(element){ + let ret = { + resourceUri: element.resource, + contextValue: element.resource.fsPath, + label: element.label, + iconPath: element.iconPath, + collapsibleState: element.collapsibleState, + command: element.type === FilePathTreeDataProvider.TYPE_FILE ? { + command: 'solidity-va.cockpit.jumpToRange', + arguments: [element.resource], + title: 'JumpTo' + } : 0, + }; + return ret; + } + + /** other methods */ + refresh(){ + return new Promise((resolve, reject) => { + vscode.workspace.findFiles("{**/*_flat.sol,**/flat_*.sol}", settings.DEFAULT_FINDFILES_EXCLUDES_ALLOWFLAT, 500) + .then((solfiles) => { + this.load(solfiles); + this._onDidChangeTreeData.fire(); + resolve(); + }); + }); + } +} + +class FlatFilesView extends BaseView { + constructor(cockpit) { + super(); + this.cockpit = cockpit; + this.id = "flatFiles"; + this.dataProvider = new FlatFilesDataProvider(this); + this.treeView = vscode.window.createTreeView(`solidity-va-cockpit-${this.id}`, { treeDataProvider:this.dataProvider, showCollapseAll:true, canSelectMany:true }); + } +} + +/* settings view */ +class SettingsViewDataProvider extends VirtualPathTreeDataProvider { + constructor(treeView){ + super("tree", "."); + this.treeView = treeView; + this._onDidChangeTreeData = new vscode.EventEmitter(); + this.onDidChangeTreeData = this._onDidChangeTreeData.event; + + this.data = null; + + let pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "..","..",'package.json'))); + let properties = pkg.contributes.configuration.properties; + this.settings = Object.keys(properties) + .filter(key => properties[key].type === "boolean") + .reduce((obj, key) => { + obj[key] = properties[key]; + return obj; + }, {}); + } + + async dataGetRoot(){ + if(this.data === null){ + this.refresh(); + } + return this.data || []; + } + + /** events */ + + /** tree methods */ + // inherited. + + getTreeItem(element){ + let ret = { + resourceUri: element.resource, + metadata: element.metadata, + contextValue: element.type, + label: element.type === VirtualPathTreeDataProvider.TYPE_LEAF ? (element.metadata.currentValue===true ? "☑ " : "☐ ") + element.label : element.label, + tooltip: element.type === VirtualPathTreeDataProvider.TYPE_LEAF ? element.metadata.description : null, + iconPath: element.iconPath, + collapsibleState: element.collapsibleState, + command: element.type === VirtualPathTreeDataProvider.TYPE_LEAF ? { + command: 'solidity-va.cockpit.settings.toggle', + arguments: [element], + title: 'Toggle' + } : 0, + }; + return ret; + } + + /** other methods */ + refresh(){ + return new Promise((resolve, reject) => { + let settingsState = Object.keys(this.settings) + .reduce((obj, key) => { + obj[key] = this.settings[key]; + let k = key.split("."); + obj[key].extension = k[0]; + obj[key].section = k.slice(1).join("."); + obj[key].currentValue = vscode.workspace.getConfiguration(obj[key].extension).get(obj[key].section); + return obj; + }, {}); + this.load(settingsState); + this._onDidChangeTreeData.fire(); + resolve(); + }); + } +} + +class SettingsView extends BaseView { + constructor(cockpit) { + super(); + this.cockpit = cockpit; + this.id = "settings"; + this.dataProvider = new SettingsViewDataProvider(this); + this.treeView = vscode.window.createTreeView(`solidity-va-cockpit-${this.id}`, { treeDataProvider:this.dataProvider, showCollapseAll:true }); + } +} + +/** -- cockpit handler -- */ +class Cockpit { + + constructor(commands) { + this.commands = commands; + this.views = {}; + + this.registerView(new ExplorerView(this)); + this.registerView(new TopLevelContractsView(this)); + this.registerView(new FlatFilesView(this)); + this.registerView(new FTraceView(this)); + this.registerView(new SettingsView(this)); + this.registerView(new PublicMethodsView(this)); + } + + registerView(view) { + this.views[view.id] = view; + } + + async onDidSelectionChange(event){ + + if(event.textEditor._visibleRanges.length <= 0 || event.selections.length <= 0){ + return; // no visible range open; no selection + } + + Object.keys(this.views).forEach(k => { + let v = this.views[k]; + if(v.treeView.visible){ + v.onDidSelectionChange(event); + } + }); + } +} + + +module.exports = { + Cockpit: Cockpit +}; \ No newline at end of file diff --git a/src/features/commands.js b/src/features/commands.js index a5a0824..6c580c2 100644 --- a/src/features/commands.js +++ b/src/features/commands.js @@ -116,24 +116,56 @@ class Commands{ .then(doc => vscode.window.showTextDocument(doc, vscode.ViewColumn.Beside)); } - async surya(document, command, args) { - // run surya and maybe return output in new window - this._checkIsSolidity(document); // throws - + async surya(documentOrListItems, command, args) { + //check if input was document or listItem + if(!documentOrListItems){ + throw new Error("not a file or list item"); + } + let ret; + let files = []; - let files; + if(documentOrListItems.hasOwnProperty("children")){ + //hack ;) + documentOrListItems = [documentOrListItems]; //allow non array calls + } - if(settings.extensionConfig().tools.surya.input.contracts=="workspace"){ - await vscode.workspace.findFiles("**/*.sol",'**/node_modules', 500) - .then(uris => { - files = uris.map(function (uri) { - return uri.fsPath; - }); - }); + if(Array.isArray(documentOrListItems)){ + + for(let documentOrListItem of documentOrListItems){ + + if(documentOrListItem.hasOwnProperty("children")){ + // is a list item -> item.resource.fsPath + if(!!path.extname(documentOrListItem.resource.fsPath)){ + //file + files = [...files, documentOrListItem.resource.fsPath]; + } else { + //folder + await vscode.workspace.findFiles(`${documentOrListItem.path}/**/*.sol`, settings.DEFAULT_FINDFILES_EXCLUDES, 500) + .then(uris => { + files = files.concat(uris.map(function (uri) { + return uri.fsPath; + })); + }); + } + } + + } } else { - files = [document.uri.fsPath, ...Object.keys(this.g_parser.sourceUnits)]; //better only add imported files. need to resolve that somehow - } + //single document mode + this._checkIsSolidity(documentOrListItems); // throws + + if(settings.extensionConfig().tools.surya.input.contracts=="workspace"){ + await vscode.workspace.findFiles("**/*.sol", settings.DEFAULT_FINDFILES_EXCLUDES, 500) + .then(uris => { + files = uris.map(function (uri) { + return uri.fsPath; + }); + }); + } else { + files = [documentOrListItems.uri.fsPath, ...Object.keys(this.g_parser.sourceUnits)]; //better only add imported files. need to resolve that somehow + } + } switch(command) { case "describe": @@ -242,6 +274,9 @@ class Commands{ break; case "mdreport": ret = surya.mdreport(files); + if(!ret) { + return; + } vscode.workspace.openTextDocument({content: ret, language: "markdown"}) .then(doc => { if(settings.extensionConfig().preview.markdown){ @@ -267,18 +302,26 @@ class Commands{ } } - async _findTopLevelContracts(files, scanfiles) { + async _findTopLevelContracts(files, scanfiles, workspaceRelativeBaseDirs) { var that = this; var dependencies={}; var contractToFile={}; if(!scanfiles){ - await vscode.workspace.findFiles("**/*.sol",'{**/node_modules,**/mock*,**/test*,**/migrations,**/Migrations.sol,**/flat_*.sol}', 500) + + workspaceRelativeBaseDirs = Array.isArray(workspaceRelativeBaseDirs) ? workspaceRelativeBaseDirs : [workspaceRelativeBaseDirs]; + + let searchFileString = "{" +workspaceRelativeBaseDirs.map(d => d === undefined ? "**/*.sol" : d + path.sep + "**/*.sol").join(",") + "}"; + + await vscode.workspace.findFiles(searchFileString, settings.DEFAULT_FINDFILES_EXCLUDES, 500) .then((solfiles) => { solfiles.forEach(function(solfile){ try { - let content = fs.readFileSync(solfile.path).toString('utf-8'); + let content = fs.readFileSync(solfile.fsPath).toString('utf-8'); let sourceUnit = that.g_parser.parseSourceUnit(content); for(let contractName in sourceUnit.contracts){ + if(sourceUnit.contracts[contractName]._node.kind == "interface") { //ignore interface contracts + continue; + } dependencies[contractName] = sourceUnit.contracts[contractName].dependencies; contractToFile[contractName] = solfile; } @@ -291,6 +334,9 @@ class Commands{ //files not set: take loaded sourceUnits from this.g_parser //files set: only take these sourceUnits for(let contractName in this.g_parser.contracts){ + if(this.g_parser.contracts[contractName]._node.kind == "interface") { + continue; + } dependencies[contractName] = this.g_parser.contracts[contractName].dependencies; } } @@ -397,9 +443,11 @@ ${topLevelContractsText}`; }); } - async flattenCandidates() { - let topLevelContracts = await this._findTopLevelContracts(); + async flattenCandidates(candidates) { + // takes object key=contractName value=fsPath + let topLevelContracts = candidates || await this._findTopLevelContracts(); let content = ""; + let trufflepath = undefined; this.solidityFlattener(Object.values(topLevelContracts), (filepath, trufflepath, content) => { @@ -415,8 +463,9 @@ ${topLevelContractsText}`; for(let name in topLevelContracts){ //this.flaterra(new vscode.Uri(topLevelContracts[name])) - let outpath = path.parse(topLevelContracts[name].path); - content += name + " => " + vscode.Uri.file(path.join(outpath.dir, "flat_" + outpath.base)) + "\n"; + let outpath = path.parse(topLevelContracts[name].fsPath); + let outpath_flat = vscode.Uri.file(path.join(outpath.dir, "flat_" + outpath.base)); + content += `${!fs.existsSync(outpath_flat.fsPath)?"[ERR] ":""}${name} => ${outpath_flat} \n`; } vscode.workspace.openTextDocument({content: content, language: "markdown"}) .then(doc => vscode.window.showTextDocument(doc, vscode.ViewColumn.Beside)); @@ -453,7 +502,7 @@ ${topLevelContractsText}`; let sighashes = {}; let collisions = []; - await vscode.workspace.findFiles("**/*.sol",'**/node_modules', 500) + await vscode.workspace.findFiles("**/*.sol", settings.DEFAULT_FINDFILES_EXCLUDES, 500) .then(uris => { uris.forEach(uri => { try { diff --git a/src/features/genericDiag.js b/src/features/genericDiag.js index a5833ff..20dc275 100644 --- a/src/features/genericDiag.js +++ b/src/features/genericDiag.js @@ -128,7 +128,8 @@ class DiliDiagnosticCollection { }); } catch (err) { - console.error(err); + console.warn(f); + console.warn(err); } }); }); diff --git a/src/features/parser.js b/src/features/parser.js index dfbdf44..0691df4 100644 --- a/src/features/parser.js +++ b/src/features/parser.js @@ -52,6 +52,57 @@ const reservedKeywords = [ "typeof", "unchecked"]; +class SourceUnit { + constructor(parser){ + this.parser = parser; + this.contracts = {}; + this.pragmas = []; + this.imports = []; + this.hash = undefined; + this.filepath = undefined; + this.commentMapper = undefined; + } + + getContractAtLocation(line, column){ + for(let c of Object.keys(this.contracts)){ + let loc = this.contracts[c]._node.loc; + if(line < loc.start.line){ + continue; + } else if (line == loc.start.line && column < loc.start.column) { + continue; + } else if (line == loc.end.line && column > loc.end.column) { + continue; + } else if (line > loc.end.line){ + continue; + } + + return this.contracts[c]; + } + } + + getFunctionAtLocation(line, column){ + let contract = this.getContractAtLocation(line, column); + if(!contract){ + return; + } + for(let c of Object.keys(contract.functions)){ + let loc = contract.functions[c]._node.loc; + if(line < loc.start.line){ + continue; + } else if (line == loc.start.line && column < loc.start.column) { + continue; + } else if (line == loc.end.line && column > loc.end.column) { + continue; + } else if (line > loc.end.line){ + continue; + } + + return {contract, function:contract.functions[c]}; + } + return {contract}; + } +} + class SolidityParser{ constructor() { @@ -103,7 +154,7 @@ class SolidityParser{ sourceUnit.imports.forEach(function(imp){ //basedir - let fileWorkspace = vscode.workspace.getWorkspaceFolder(vscode.Uri.file(filepath)).path; + let fileWorkspace = vscode.workspace.getWorkspaceFolder(vscode.Uri.file(filepath)).uri.fsPath; let relativeNodeModules = function(){ let basepath = filepath.split("/contracts/"); @@ -204,12 +255,7 @@ class SolidityParser{ console.error("solidity-parser-diligence - failed to parse input"); } - var sourceUnit = { - contracts:{}, - pragmas:[], - imports:[], - hash:null - }; + let sourceUnit = new SourceUnit(this); /** AST rdy */ var current_contract=null; diff --git a/src/features/whatsnew/whatsNew.js b/src/features/whatsnew/whatsNew.js index ea5c7a0..b65e093 100644 --- a/src/features/whatsnew/whatsNew.js +++ b/src/features/whatsnew/whatsNew.js @@ -21,10 +21,43 @@ Hey there 🙌 **Solidity Visual Auditor** just got better! Find out more below. ### What's New? +We've been working on a new cockpit view that allows you to navigate large codebases more efficiently. Check out the new  sidebar-logo  icon in the activity bar to your left. + +So, what can you do with it? + +- Explore .sol files with the new workspace explorer +- Generate report/graphs for any files/folders selected in the explorer views +- Conveniently flatten selected files (selected folders or all files in the top-level view) (Note: \`truffle-flattener\` may require an \`npm install\` of the project for flattening to work) +- Search for contracts that are likely to be deployed in the system (complete workspace or selected folders) + + image + +- Context-sensitive views: click into a contract in the editor to list public state-changing methods + + image + +- Get quick access to extension settings + + image + +And there is more to come 🙌 stay tuned! + + +The cockpit view is fully customizable. You can hide both the sidebar menu or any view in the cockpit that you do not need (right-click → hide). + + The complete changelog can be found [here](https://github.com/ConsenSys/vscode-solidity-auditor/blob/master/CHANGELOG.md). #### v0.0.23 - new: Update notifications have arrived! +- updated: solidity parser and surya +- new: 🔥 Solidity Visual Auditor Cockpit panel + - Workspace Explorer + - Quick-access to extension settings + - Find Top Level Contracts + - Keep track of flattened files + - List public state-changing methods from the current contract + - Show the function call trace for the current method Hint: disable future notification? \`settings → solidity-va.whatsNew.disabled : true\` diff --git a/src/settings.js b/src/settings.js index 0386c28..c236496 100644 --- a/src/settings.js +++ b/src/settings.js @@ -11,6 +11,9 @@ const docSelector = { language: languageId }; +const DEFAULT_FINDFILES_EXCLUDES = '{**/node_modules,**/mock*,**/test*,**/migrations,**/Migrations.sol,**/flat_*.sol}'; +const DEFAULT_FINDFILES_EXCLUDES_ALLOWFLAT = '{**/node_modules,**/mock*,**/test*,**/migrations,**/Migrations.sol}'; + function extensionConfig() { return vscode.workspace.getConfiguration('solidity-va'); } @@ -23,5 +26,7 @@ module.exports = { extensionConfig: extensionConfig, languageId: languageId, docSelector: docSelector, - extension: extension + extension: extension, + DEFAULT_FINDFILES_EXCLUDES: DEFAULT_FINDFILES_EXCLUDES, + DEFAULT_FINDFILES_EXCLUDES_ALLOWFLAT: DEFAULT_FINDFILES_EXCLUDES_ALLOWFLAT }; \ No newline at end of file diff --git a/whats_new/icon.png b/whats_new/icon.png new file mode 100644 index 0000000..a7b1a62 Binary files /dev/null and b/whats_new/icon.png differ diff --git a/whats_new/index.html b/whats_new/index.html index d0fdf28..0400149 100644 --- a/whats_new/index.html +++ b/whats_new/index.html @@ -29,9 +29,9 @@ line-height: 1.6; padding-top: 10px; padding-bottom: 10px; - background-color: white; + x-background-color: white; padding: 30px; - color: #333; + x-color: #333; } body > *:first-child { @@ -409,7 +409,6 @@ background-color: transparent; border: none; } - body { background-color: white; color: black} - +
\ No newline at end of file