// ==UserScript== // @name scratch - show unused blocks // @version 0.3 // @description adds a unused blocks tab to the debug panel of scratch addons that shows all custom blocks that exist but are not called and all that call to a nonexistant custom block // @license GPLv3 // @run-at document-start // @author You // @match *://scratch.mit.edu/* // @icon https://scratch.mit.edu/favicon.ico // @grant none // @namespace https://greasyfork.org/users/1184528 // @downloadURL none // ==/UserScript== ;(async () => { await a(".sa-debugger-tabs>li").waitforelem() var unusedblocks = a(".sa-debugger-tabs").createelem("li", { class: "scratchCategoryId-myBlocks", innerHTML: `
unused blocks`, onclick(e) { e.stopImmediatePropagation() a(".sa-debugger-tab-selected") .qs() .val.classList.remove("sa-debugger-tab-selected") this.classList.add("sa-debugger-tab-selected") var content = a(".sa-debugger-tab-content").qs().val content.innerHTML = "" var arrs = {} vm.runtime.targets.forEach((target) => Object.entries(target.blocks._blocks) .filter( (target) => target[1].opcode == "procedures_call" || target[1].opcode == "procedures_prototype" ) .forEach((e) => { if (!arrs[target.sprite.name]) arrs[target.sprite.name] = { procedures_prototype: [], procedures_call: [], } arrs[target.sprite.name][e[1].opcode].push(e[1]?.mutation?.proccode) }) ) var parents = {} Object.entries(arrs).forEach( ([name, { procedures_call, procedures_prototype }]) => { procedures_call.forEach((call) => { if (!procedures_prototype.includes(call)) { if ( [ "breakpoint", "​​warn​​ %s", "​​log​​ %s", "​​error​​ %s", "​​breakpoint​​", ].includes(call) ) return var parent = parents[name] ?? a(content).createelem("div", { id: "ubpar" + name, width: "calc(100% - 8px)", border: vm.editingTarget.sprite.name == name ? "4px solid #090" : "4px solid #900", backgroundColor: vm.editingTarget.sprite.name == name ? "#070" : "#700", innerHTML: `${name}`, }) parents[name] ??= parent a(parent).createelem("div", { width: "calc(100% - 48px)", position: "relative", left: "40px", border: "4px solid #990", backgroundColor: "#770", innerHTML: `call to non existent function "${call}"`, onclick() { if (vm.editingTarget.sprite.name == name) goto("define " + call) }, }) } }) procedures_prototype.forEach((prototype) => { if (!procedures_call.includes(prototype)) { var parent = parents[name] ?? a(content).createelem("div", { id: "ubpar" + name, width: "calc(100% - 8px)", border: vm.editingTarget.sprite.name == name ? "4px solid #090" : "4px solid #900", backgroundColor: vm.editingTarget.sprite.name == name ? "#070" : "#700", innerHTML: `${name}`, }) parents[name] ??= parent a(parent).createelem("div", { width: "calc(100% - 48px)", position: "relative", left: "40px", border: "4px solid #990", backgroundColor: "#770", innerHTML: `unused function "${prototype}"`, onclick() { if (vm.editingTarget.sprite.name == name) goto("define " + prototype) }, }) } }) } ) }, }).val a(".sa-debugger-tabs>li") .qsa() .map((e) => a(e).listen("click", function (e) { log(e.target) if (e.target !== unusedblocks) unusedblocks.classList.remove("sa-debugger-tab-selected") }) ) function goto(text) { var find = a("#sa-find-input").qs().val find.value = text // find.blur() find.focus() a('.sa-find-dropdown>li[style="display: block;"]') .qs() .val.dispatchEvent( new MouseEvent("mousedown", { bubbles: true, cancelable: true, view: window, }) ) } })()