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

  1. // ==UserScript==
  2. // @name scratch - show unused blocks
  3. // @version 2
  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
  5. // @license GPLv3
  6. // @tag more info
  7. // @run-at document-start
  8. // @author rssaromeo
  9. // @match *://scratch.mit.edu/*
  10. // @icon https://scratch.mit.edu/favicon.ico
  11. // @grant none
  12. // @namespace https://greasyfork.org/users/1184528
  13. // ==/UserScript==
  14. ;(async () => {
  15. const a = loadlib("newallfuncs")
  16. await loadlib("libloader").waitforlib("scratch")
  17. const { vm } = loadlib("scratch")
  18. await a.waitforelem(".sa-debugger-tabs>li")
  19. var unusedblocks = a.createelem(".sa-debugger-tabs", "li", {
  20. class: "scratchCategoryId-myBlocks",
  21. innerHTML: `<div class="scratchCategoryItemBubble" style="background-color: rgb(94, 30, 63); border-color: rgb(255, 0, 132);"></div>unused blocks`,
  22. onclick(e) {
  23. e.stopImmediatePropagation()
  24. a.qs(".sa-debugger-tab-selected").classList.remove(
  25. "sa-debugger-tab-selected"
  26. )
  27. this.classList.add("sa-debugger-tab-selected")
  28. var content = a.qs(".sa-debugger-tab-content")
  29. content.innerHTML = ""
  30. var arrs = {}
  31. vm.runtime.targets.forEach((target) =>
  32. Object.entries(target.blocks._blocks)
  33. .filter(
  34. (target) =>
  35. target[1].opcode == "procedures_call" ||
  36. target[1].opcode == "procedures_prototype"
  37. )
  38. .forEach((e) => {
  39. if (!arrs[target.sprite.name])
  40. arrs[target.sprite.name] = {
  41. procedures_prototype: [],
  42. procedures_call: [],
  43. }
  44. arrs[target.sprite.name][e[1].opcode].push(
  45. e[1]?.mutation?.proccode
  46. )
  47. })
  48. )
  49. var parents = {}
  50. Object.entries(arrs).forEach(
  51. ([name, { procedures_call, procedures_prototype }]) => {
  52. procedures_call.forEach((call) => {
  53. if (!procedures_prototype.includes(call)) {
  54. if (
  55. [
  56. "breakpoint",
  57. "​​warn​​ %s",
  58. "​​log​​ %s",
  59. "​​error​​ %s",
  60. "​​breakpoint​​",
  61. ].includes(call)
  62. )
  63. return
  64. var parent =
  65. parents[name] ??
  66. content.appendChild(
  67. a.newelem("div", {
  68. id: "ubpar" + name,
  69. width: "calc(100% - 8px)",
  70. border:
  71. vm.editingTarget.sprite.name == name
  72. ? "4px solid #090"
  73. : "4px solid #900",
  74. backgroundColor:
  75. vm.editingTarget.sprite.name == name
  76. ? "#070"
  77. : "#700",
  78. innerHTML: `${name}`,
  79. })
  80. )
  81. parents[name] ??= parent
  82. parent.appendChild(
  83. a.newelem("div", {
  84. width: "calc(100% - 48px)",
  85. position: "relative",
  86. left: "40px",
  87. border: "4px solid #990",
  88. backgroundColor: "#770",
  89. innerHTML: `call to non existent function "${call}"`,
  90. onclick() {
  91. if (vm.editingTarget.sprite.name == name)
  92. goto("define " + call)
  93. },
  94. })
  95. )
  96. }
  97. })
  98. procedures_prototype.forEach((prototype) => {
  99. if (!procedures_call.includes(prototype)) {
  100. var parent =
  101. parents[name] ??
  102. content.appendChild(
  103. a.newelem("div", {
  104. id: "ubpar" + name,
  105. width: "calc(100% - 8px)",
  106. border:
  107. vm.editingTarget.sprite.name == name
  108. ? "4px solid #090"
  109. : "4px solid #900",
  110. backgroundColor:
  111. vm.editingTarget.sprite.name == name
  112. ? "#070"
  113. : "#700",
  114. innerHTML: `${name}`,
  115. })
  116. )
  117. parents[name] ??= parent
  118. parent.appendChild(
  119. a.newelem("div", {
  120. width: "calc(100% - 48px)",
  121. position: "relative",
  122. left: "40px",
  123. border: "4px solid #990",
  124. backgroundColor: "#770",
  125. innerHTML: `unused function "${prototype}"`,
  126. onclick() {
  127. if (vm.editingTarget.sprite.name == name)
  128. goto("define " + prototype)
  129. },
  130. })
  131. )
  132. }
  133. })
  134. }
  135. )
  136. },
  137. })
  138. a.qsa(".sa-debugger-tabs>li").map((e) =>
  139. a.listen(e, "click", function (e) {
  140. log(e.target)
  141. if (e.target !== unusedblocks)
  142. unusedblocks.classList.remove("sa-debugger-tab-selected")
  143. })
  144. )
  145. function goto(text) {
  146. var find = a.qs("#sa-find-input")
  147. find.value = text
  148. // find.blur()
  149. find.focus()
  150. a.qs(
  151. '.sa-find-dropdown>li[style="display: block;"]'
  152. ).dispatchEvent(
  153. new MouseEvent("mousedown", {
  154. bubbles: true,
  155. cancelable: true,
  156. view: window,
  157. })
  158. )
  159. }
  160. })()