From 35ef4bd71c59f0180f5399b471479eab8f52f61c Mon Sep 17 00:00:00 2001 From: JohnnyMorganz Date: Sat, 30 Apr 2022 14:53:03 +0100 Subject: [PATCH] Add support for requiring JSON modules Closes Add JSON require support #28 --- README.md | 1 + RequireResolver.cpp | 32 +++++++++++++++--- RequireResolver.h | 3 ++ main.cpp | 81 ++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 109 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 0c3b9e1..b601b47 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ python dumpRobloxTypes.py > globalTypes.d.lua - Full type definitions through API dumps - Proper resolution of `Instance.new("Class")` and `game:GetService("Service")` to their associated types - `Instance:IsA("ClassName")` type refinements +- JSON Module requiring support **Note: in non-strict mode, some unknown requires are silently ignored. If it seems a require hasn't resolved correctly, use strict mode (`--!strict`) to warn about unknown requires (i.e., ones which were not able to resolve to a virtual path)** diff --git a/RequireResolver.cpp b/RequireResolver.cpp index 45b248e..8b1083c 100644 --- a/RequireResolver.cpp +++ b/RequireResolver.cpp @@ -200,7 +200,7 @@ void from_json(const json& j, SourceNode& p) * * @param node SourceNode to find relevant path from */ -std::optional getRelevantFilePath(const SourceNode& node) +std::optional RojoResolver::getRelevantFilePath(const SourceNode& node) { for (const auto& path : node.filePaths) { @@ -208,11 +208,15 @@ std::optional getRelevantFilePath(const SourceNode& node) { return path; } + else if (path.extension() == ".json" && (node.className == "ModuleScript" || node.className == "Script" || node.className == "LocalScript")) + { + return path; + } } return std::nullopt; } -std::optional> findChildWithName(const SourceNode& node, const std::string_view& name) +std::optional> RojoResolver::findChildWithName(const SourceNode& node, const std::string_view& name) { for (const auto& child : node.children) { @@ -228,7 +232,7 @@ std::optional> findChildWithName(const SourceNode& n void dumpSourceMap(const SourceNode& root, int level = 0) { printf("%*s%s\n", level, "", root.name.c_str()); - if (auto path = getRelevantFilePath(root)) + if (auto path = RojoResolver::getRelevantFilePath(root)) { try { @@ -260,7 +264,7 @@ void dumpSourceMap(const SourceNode& root, int level = 0) void writePathsToMap(const SourceNode& node, std::string base, std::unordered_map& map) { - if (auto path = getRelevantFilePath(node)) + if (auto path = RojoResolver::getRelevantFilePath(node)) { try @@ -414,6 +418,26 @@ static bool endsWith(std::string str, std::string suffix) return str.size() >= suffix.size() && 0 == str.compare(str.size() - suffix.size(), suffix.size(), suffix); } +Luau::SourceCode::Type RojoResolver::sourceCodeTypeFromClassName(const std::string& className) +{ + if (className == "ServerScript") + { + return Luau::SourceCode::Type::Script; + } + else if (className == "LocalScript") + { + return Luau::SourceCode::Type::Local; + } + else if (className == "ModuleScript") + { + return Luau::SourceCode::Type::Module; + } + else + { + return Luau::SourceCode::Type::None; + } +} + Luau::SourceCode::Type RojoResolver::sourceCodeTypeFromPath(const std::filesystem::path& requirePath) { auto filename = requirePath.filename().generic_string(); diff --git a/RequireResolver.h b/RequireResolver.h index e7d2239..eb07ee0 100644 --- a/RequireResolver.h +++ b/RequireResolver.h @@ -30,6 +30,9 @@ std::optional parseProjectFile(const std::filesystem::path& p std::optional parseSourceMap(const std::filesystem::path& projectFilePath); std::optional resolveRequireToSourceNode(const std::string& requirePath, const SourceNode& root); std::optional resolveRequireToRealPath(const std::string& requirePath, const SourceNode& root); +std::optional getRelevantFilePath(const SourceNode& node); +std::optional> findChildWithName(const SourceNode& node, const std::string_view& name); +Luau::SourceCode::Type sourceCodeTypeFromClassName(const std::string& className); Luau::SourceCode::Type sourceCodeTypeFromPath(const std::filesystem::path& path); std::optional resolveRealPathToVirtual(const ResolvedSourceMap& sourceMap, const std::filesystem::path& filePath); }; // namespace RojoResolver diff --git a/main.cpp b/main.cpp index 19cfd5d..347d871 100644 --- a/main.cpp +++ b/main.cpp @@ -5,6 +5,7 @@ #include "Luau/Frontend.h" #include "Luau/TypeAttach.h" #include "Luau/Transpiler.h" +#include "extern/json.hpp" #include #include @@ -21,6 +22,7 @@ LUAU_FASTINT(LuauTypeInferIterationLimit) LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTFLAG(DebugLuauFreezeArena) +using json = nlohmann::json; enum class ReportFormat { @@ -162,6 +164,48 @@ bool isManagedModule(const Luau::ModuleName& name) return Luau::startsWith(name, "game/") || Luau::startsWith(name, "ProjectRoot/"); } +std::string jsonValueToLuau(const json& val) +{ + if (val.is_string() || val.is_number() || val.is_boolean()) + { + return val.dump(); + } + else if (val.is_null()) + { + return "nil"; + } + else if (val.is_array()) + { + std::string out = "{"; + for (auto& elem : val) + { + out += jsonValueToLuau(elem); + out += ";"; + } + + out += "}"; + return out; + } + else if (val.is_object()) + { + std::string out = "{"; + + for (auto& [key, val] : val.items()) + { + out += "[\"" + key + "\"] = "; + out += jsonValueToLuau(val); + out += ";"; + } + + out += "}"; + return out; + } + else + { + return ""; // TODO: should we error here? + } +} + std::optional getCurrentModuleVirtualPath( const Luau::ModuleName& name, ResolvedSourceMap sourceMap, std::optional stdinFilepath) { @@ -218,11 +262,40 @@ struct CliFileResolver : Luau::FileResolver } else if (isManagedModule(name)) { - std::optional realFilePath = RojoResolver::resolveRequireToRealPath(name, sourceMap.root); - if (realFilePath.has_value()) + std::optional sourceNode = RojoResolver::resolveRequireToSourceNode(name, sourceMap.root); + if (sourceNode.has_value()) { - source = readFile(realFilePath.value()); - sourceType = RojoResolver::sourceCodeTypeFromPath(realFilePath.value()); + auto path = RojoResolver::getRelevantFilePath(sourceNode.value()); + if (path.has_value()) + { + source = readFile(path.value()); + + // Handle if its a .json file, convert it into a Luau Module + if (source.has_value() && path.value().extension() == ".json") + { + try + { + auto obj = json::parse(source.value()); + source = "return " + jsonValueToLuau(obj); + } + catch (const std::exception& ex) + { + fprintf(stderr, "Failed to load JSON module %s: %s\n", path.value().generic_string().c_str(), ex.what()); + return std::nullopt; + } + } + + if (sourceNode.value().className.has_value()) + { + + sourceType = RojoResolver::sourceCodeTypeFromClassName(sourceNode.value().className.value()); + } + else + { + + sourceType = RojoResolver::sourceCodeTypeFromPath(path.value()); + } + } } } else