Skip to content
This repository has been archived by the owner on Jun 12, 2022. It is now read-only.

Commit

Permalink
Add support for requiring JSON modules
Browse files Browse the repository at this point in the history
Closes Add JSON require support #28
  • Loading branch information
JohnnyMorganz committed Apr 30, 2022
1 parent 9598fda commit 35ef4bd
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 8 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)**

Expand Down
32 changes: 28 additions & 4 deletions RequireResolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -200,19 +200,23 @@ void from_json(const json& j, SourceNode& p)
*
* @param node SourceNode to find relevant path from
*/
std::optional<std::filesystem::path> getRelevantFilePath(const SourceNode& node)
std::optional<std::filesystem::path> RojoResolver::getRelevantFilePath(const SourceNode& node)
{
for (const auto& path : node.filePaths)
{
if (path.extension() == ".lua" || path.extension() == ".luau")
{
return path;
}
else if (path.extension() == ".json" && (node.className == "ModuleScript" || node.className == "Script" || node.className == "LocalScript"))
{
return path;
}
}
return std::nullopt;
}

std::optional<std::shared_ptr<SourceNode>> findChildWithName(const SourceNode& node, const std::string_view& name)
std::optional<std::shared_ptr<SourceNode>> RojoResolver::findChildWithName(const SourceNode& node, const std::string_view& name)
{
for (const auto& child : node.children)
{
Expand All @@ -228,7 +232,7 @@ std::optional<std::shared_ptr<SourceNode>> 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
{
Expand Down Expand Up @@ -260,7 +264,7 @@ void dumpSourceMap(const SourceNode& root, int level = 0)

void writePathsToMap(const SourceNode& node, std::string base, std::unordered_map<std::string, std::string>& map)
{
if (auto path = getRelevantFilePath(node))
if (auto path = RojoResolver::getRelevantFilePath(node))
{

try
Expand Down Expand Up @@ -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();
Expand Down
3 changes: 3 additions & 0 deletions RequireResolver.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ std::optional<ResolvedSourceMap> parseProjectFile(const std::filesystem::path& p
std::optional<ResolvedSourceMap> parseSourceMap(const std::filesystem::path& projectFilePath);
std::optional<SourceNode> resolveRequireToSourceNode(const std::string& requirePath, const SourceNode& root);
std::optional<std::filesystem::path> resolveRequireToRealPath(const std::string& requirePath, const SourceNode& root);
std::optional<std::filesystem::path> getRelevantFilePath(const SourceNode& node);
std::optional<std::shared_ptr<SourceNode>> 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<std::string> resolveRealPathToVirtual(const ResolvedSourceMap& sourceMap, const std::filesystem::path& filePath);
}; // namespace RojoResolver
Expand Down
81 changes: 77 additions & 4 deletions main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "Luau/Frontend.h"
#include "Luau/TypeAttach.h"
#include "Luau/Transpiler.h"
#include "extern/json.hpp"
#include <filesystem>
#include <vector>

Expand All @@ -21,6 +22,7 @@ LUAU_FASTINT(LuauTypeInferIterationLimit)
LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(DebugLuauFreezeArena)

using json = nlohmann::json;

enum class ReportFormat
{
Expand Down Expand Up @@ -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<std::string> getCurrentModuleVirtualPath(
const Luau::ModuleName& name, ResolvedSourceMap sourceMap, std::optional<std::filesystem::path> stdinFilepath)
{
Expand Down Expand Up @@ -218,11 +262,40 @@ struct CliFileResolver : Luau::FileResolver
}
else if (isManagedModule(name))
{
std::optional<std::filesystem::path> realFilePath = RojoResolver::resolveRequireToRealPath(name, sourceMap.root);
if (realFilePath.has_value())
std::optional<SourceNode> 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
Expand Down

0 comments on commit 35ef4bd

Please sign in to comment.