Greasy Fork is available in English.

scratch - show unused blocks

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

// ==UserScript==
// @name         scratch - show unused blocks
// @version      0.4
// @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
// ==/UserScript==
;(async () => {
  const a = loadlib('allfuncs')
  const {vm} = loadlib('scratch')
await a(".sa-debugger-tabs>li").waitforelem()
  var unusedblocks = a(".sa-debugger-tabs").createelem("li", {
    class: "scratchCategoryId-myBlocks",
    innerHTML: `<div class="scratchCategoryItemBubble" style="background-color: rgb(94, 30, 63); border-color: rgb(255, 0, 132);"></div>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,
        })
      )
  }
})()