diff --git a/.gitignore b/.gitignore index 5a9b652..75b7296 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ /node_modules/ /vscExtension/node_modules/ -/vscExtension/out/ diff --git a/vscExtension/out/extension.js b/vscExtension/out/extension.js new file mode 100644 index 0000000..c46f1dd --- /dev/null +++ b/vscExtension/out/extension.js @@ -0,0 +1,608 @@ +"use strict" + +const vscode = require("vscode") + +let outputChannel +const log = message => outputChannel.appendLine(message) +let treeView + +let versions = [] +const requestVersions = async () => { + const res = await fetch("https://raw.githubusercontent.com/misode/mcmeta/summary/versions/data.json", { + headers: { + Accept: "application/json", + "User-Agent": "DEVTomatoCake/Pack-Analyzer" + } + }) + const json = await res.json() + + versions = json.map(ver => ({ + name: ver.id, + datapack_version: ver.data_pack_version, + resourcepack_version: ver.resource_pack_version + })) +} +requestVersions() + +let interval +let files = 0 +let done = 0 +let error = 0 +let rpMode = false + +let filetypes = {} +let filetypesOther = {} +let packFiles = [] +let packImages = [] +let commands = {} +let cmdsBehindExecute = {} +let cmdsBehindMacros = {} +let cmdsBehindReturn = {} +let comments = 0 +let empty = 0 +let emptyFiles = [] +let dpExclusive = { + folders: { + advancements: 0, + loot_tables: 0, + recipes: 0, + predicates: 0, + dimension: 0, + dimension_type: 0, + worldgen: 0 + }, + tags: { + banner_pattern: 0, + blocks: 0, + cat_variant: 0, + entity_types: 0, + fluids: 0, + functions: 0, + game_events: 0, + instrument: 0, + items: 0, + painting_variant: 0, + point_of_interest_type: 0, + worldgen: 0 + }, + scoreboards: 0, + selectors: { + a: 0, + e: 0, + p: 0, + r: 0, + s: 0 + }, + functions: ["#minecraft:load", "#minecraft:tick"], + functionCalls: [{target: "#minecraft:load"}, {target: "#minecraft:tick"}] +} +let rpExclusive = { + atlases: 0, + blockstates: 0, + font: 0, + lang: 0, + models: 0, + particles: 0, + shaders: 0, + sounds: 0, + texts: 0, + textures: 0 +} + +const localize = str => str.toLocaleString() + +async function processEntries(entries) { + for await (const filePath of entries) { + const name = filePath.split("/").pop() + + const ext = name.split(".").pop() + if ( + ext == "mcmeta" || ext == "json" || + (!rpMode && (ext == "mcfunction" || ext == "nbt")) || + (rpMode && (ext == "png" || ext == "icns" || ext == "txt" || ext == "ogg" || ext == "fsh" || ext == "vsh" || ext == "glsl" || ext == "lang" || ext == "properties" || ext == "inc" || ext == "xcf")) + ) { + if (filetypes[ext]) filetypes[ext]++ + else filetypes[ext] = 1 + } else { + if (filetypesOther[(name.includes(".") ? "." : "") + ext]) filetypesOther[(name.includes(".") ? "." : "") + ext]++ + else filetypesOther[(name.includes(".") ? "." : "") + ext] = 1 + } + + if ( + ext == "mcfunction" || ext == "mcmeta" || (!rpMode && ext == "json" && (filePath.includes("/advancements/") || filePath.includes("/tags/functions/"))) || + ext == "fsh" || ext == "vsh" || ext == "glsl" || name.endsWith("pack.png") + ) { + files++ + + const processFile = result => { + log("Processing file " + filePath + " (" + result.length + " characters)") + done++ + if (result.trim() == "") return emptyFiles.push(filePath) + + if (!rpMode && ext == "mcfunction") { + const fileLocation = /data\/([-a-z0-9_.]+)\/functions\/([-a-z0-9_./]+)\.mcfunction/i.exec(filePath) + if (fileLocation && !dpExclusive.functions.includes(fileLocation[1] + ":" + fileLocation[2])) dpExclusive.functions.push(fileLocation[1] + ":" + fileLocation[2]) + + for (let line of result.split("\n")) { + line = line.trim() + if (line.startsWith("#")) comments++ + if (line == "") empty++ + if (line.startsWith("#") || line == "") continue + const splitted = line.split(" ") + + let cmd = splitted[0] + if (cmd.startsWith("$")) { + cmd = cmd.slice(1) + if (cmdsBehindMacros[cmd]) cmdsBehindMacros[cmd]++ + else cmdsBehindMacros[cmd] = 1 + } + + if (commands[cmd]) commands[cmd]++ + else commands[cmd] = 1 + + if (cmd == "execute") { + const matches = / run ([a-z_:]{2,})/g.exec(line) + if (matches) matches.forEach(match => { + const cmdBehind = match.replace("run ", "").trim() + + if (cmdsBehindExecute[cmdBehind]) cmdsBehindExecute[cmdBehind]++ + else cmdsBehindExecute[cmdBehind] = 1 + if (commands[cmdBehind]) commands[cmdBehind]++ + else commands[cmdBehind] = 1 + + if (cmdBehind == "return") { + const returnCmd = / run return run ([a-z_:]{2,})/g.exec(line) + if (returnCmd && returnCmd[1]) { + if (cmdsBehindReturn[returnCmd[1]]) cmdsBehindReturn[returnCmd[1]]++ + else cmdsBehindReturn[returnCmd[1]] = 1 + } + } + }) + } else if (cmd == "return") { + const returnCmd = / run return run ([a-z_:]{2,})/g.exec(line) + if (returnCmd && returnCmd[1]) { + if (cmdsBehindReturn[returnCmd[1]]) cmdsBehindReturn[returnCmd[1]]++ + else cmdsBehindReturn[returnCmd[1]] = 1 + } + } + if (fileLocation && (cmd == "function" || line.includes(" function ") || line.includes("/function "))) { + const func = /function ((#?[-a-z0-9_.]+):)?([-a-z0-9_./]+)/i.exec(line) + if (func && func[3]) dpExclusive.functionCalls.push({ + source: fileLocation[1] + ":" + fileLocation[2], + target: (func[2] || "minecraft") + ":" + func[3] + }) + } + + if (/scoreboard objectives add \w+ \w+( .+)?$/.test(line)) dpExclusive.scoreboards++ + + splitted.forEach(arg => { + if (arg.startsWith("@")) { + arg = arg.slice(1) + if (arg.startsWith("a")) dpExclusive.selectors.a++ + else if (arg.startsWith("e")) dpExclusive.selectors.e++ + else if (arg.startsWith("p")) dpExclusive.selectors.p++ + else if (arg.startsWith("r")) dpExclusive.selectors.r++ + else if (arg.startsWith("s")) dpExclusive.selectors.s++ + } + }) + } + } else if (ext == "mcmeta") { + if (name == "pack.mcmeta") { + try { + packFiles.push(JSON.parse(result)) + } catch (e) { + console.warn("Could not parse pack.mcmeta: " + filePath, e) + error++ + } + } + } else if (name.endsWith("pack.png") && !result.includes(">")) packImages.push(result) + else if (rpMode && (ext == "fsh" || ext == "vsh" || ext == "glsl")) { + for (let line of result.split("\n")) { + line = line.trim() + if (line.startsWith("//") || line.startsWith("/*")) comments++ + if (line == "") empty++ + if (line.startsWith("//") || line.startsWith("/*") || line == "") continue + + const cmd = line.match(/^[a-z_#0-9]+/i)?.[0] + if (cmd && cmd != "{" && cmd != "}") { + if (commands[cmd]) commands[cmd]++ + else commands[cmd] = 1 + } + } + } else if (!rpMode && ext == "json") { + if (filePath.includes("/advancements/")) { + const fileLocation = /data\/([-a-z0-9_.]+)\/advancements\/([-a-z0-9_./]+)\.json/i.exec(filePath) + + try { + const parsed = JSON.parse(result) + if (parsed.rewards && parsed.rewards.function) dpExclusive.functionCalls.push({ + source: "(Advancement) " + fileLocation[1] + ":" + fileLocation[2], + target: parsed.rewards.function.includes(":") ? parsed.rewards.function : "minecraft:" + parsed.rewards.function + }) + } catch (e) { + console.warn("Unable to analyze advancement: " + filePath, e) + } + } else if (filePath.includes("/tags/functions/")) { + const fileLocation = /data\/([-a-z0-9_.]+)\/tags\/functions\/([-a-z0-9_./]+)\.json/i.exec(filePath) + if (fileLocation && !dpExclusive.functions.includes("#" + fileLocation[1] + ":" + fileLocation[2])) dpExclusive.functions.push("#" + fileLocation[1] + ":" + fileLocation[2]) + + try { + const parsed = JSON.parse(result) + if (parsed.values) parsed.values.forEach(func => { + if (typeof func == "object") { + if (func.required === false) return + func = func.id + } + + dpExclusive.functionCalls.push({ + source: "#" + fileLocation[1] + ":" + fileLocation[2], + target: func.includes(":") ? func : "minecraft:" + func + }) + }) + } catch (e) { + console.warn("Unable to analyze function tag: " + filePath, e) + } + } + } + } + + const content = await vscode.workspace.fs.readFile(vscode.Uri.file(filePath)) + const decoder = new TextDecoder() + processFile(decoder.decode(content)) + } + if (!rpMode && ext == "json") { + Object.keys(dpExclusive.folders).forEach(type => { + if (filePath.includes("/" + type + "/")) dpExclusive.folders[type]++ + }) + Object.keys(dpExclusive.tags).forEach(type => { + if (filePath.includes("/tags/" + type + "/")) dpExclusive.tags[type]++ + }) + } else if (rpMode) + Object.keys(rpExclusive).forEach(type => { + if (filePath.includes("/" + type + "/")) rpExclusive[type]++ + }) + } + + log("Successfully processed " + done + " files with " + error + " errors") + log(JSON.stringify(filetypes, null, "\t")) + log(JSON.stringify(filetypesOther, null, "\t")) + log(JSON.stringify(dpExclusive, null, "\t")) + log(JSON.stringify(cmdsBehindExecute, null, "\t")) +} + +async function mainScan(hasData = false) { + files = 0 + done = 0 + error = 0 + rpMode = document.getElementById("radiorp").checked + + filetypes = {} + filetypesOther = {} + packFiles = [] + packImages = [] + commands = {} + cmdsBehindExecute = {} + cmdsBehindMacros = {} + cmdsBehindReturn = {} + comments = 0 + empty = 0 + emptyFiles = [] + dpExclusive = { + folders: { + advancements: 0, + loot_tables: 0, + recipes: 0, + predicates: 0, + dimension: 0, + dimension_type: 0, + worldgen: 0 + }, + tags: { + banner_pattern: 0, + blocks: 0, + cat_variant: 0, + entity_types: 0, + fluids: 0, + functions: 0, + game_events: 0, + instrument: 0, + items: 0, + painting_variant: 0, + point_of_interest_type: 0, + worldgen: 0 + }, + scoreboards: 0, + selectors: { + a: 0, + e: 0, + p: 0, + r: 0, + s: 0 + }, + functions: ["#minecraft:load", "#minecraft:tick"], + functionCalls: [{target: "#minecraft:load"}, {target: "#minecraft:tick"}] + } + rpExclusive = { + atlases: 0, + blockstates: 0, + font: 0, + lang: 0, + models: 0, + particles: 0, + shaders: 0, + sounds: 0, + texts: 0, + textures: 0 + } + + interval = setInterval(() => { + document.getElementById("progress").innerText = Math.round(done / files * 100) + "% scanned" + (error > 0 ? " - " + error + " errors" : "") + if (done + error == files || hasData) { + clearInterval(interval) + if (files == 0) return document.getElementById("progress").innerText = "No files found!" + document.getElementById("resultButtons").hidden = false + if (error == 0) document.getElementById("progress").innerText = "" + if (Object.values(filetypes).reduce((a, b) => a + b) == 0) document.getElementById("progress").innerHTML = "No " + (rpMode ? "resource" : "data") + "pack files found!" + + const uncalledFunctions = dpExclusive.functions.filter(funcName => !dpExclusive.functionCalls.some(func => func.target == funcName)) + const missingFunctions = [...new Set(dpExclusive.functionCalls.filter(func => !dpExclusive.functions.includes(func.target)).map(func => func.target))] + + let html = + (packImages.length > 0 ? "
" + feature + "
").join(", ") + ""
+ : "") +
+ (pack.filter?.block?.length > 0 ? "" + filter.namespace + "
" : "") +
+ (filter.namespace && filter.path ? ", " : "") +
+ (filter.path ? "Path: " + filter.path + "
" : "") +
+ ""
+ ).join("... run execute ...
equals ... ...
)" : "") + "