package com.oracle.truffle.js.builtins.commonjs;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.TruffleFile;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.frame.FrameInstance;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.js.builtins.GlobalBuiltins;
import com.oracle.truffle.js.lang.JavaScriptLanguage;
import com.oracle.truffle.js.runtime.Errors;
import com.oracle.truffle.js.runtime.JSArguments;
import com.oracle.truffle.js.runtime.JSContext;
import com.oracle.truffle.js.runtime.JSRealm;
import com.oracle.truffle.js.runtime.JSRuntime;
import com.oracle.truffle.js.runtime.builtins.JSFunction;
import com.oracle.truffle.js.runtime.objects.JSDynamicObject;
import com.oracle.truffle.js.runtime.objects.JSObject;
final class CommonJSResolution {
private static final String JS_EXT = ".js";
private static final String JSON_EXT = ".json";
private static final String NODE_EXT = ".node";
public static final String INDEX_JS = "index.js";
private static final String INDEX_JSON = "index.json";
private static final String INDEX_NODE = "index.node";
public static final String PACKAGE_JSON = "package.json";
private static final String NODE_MODULES = "node_modules";
public static final String PACKAGE_JSON_MAIN_PROPERTY_NAME = "main";
public static final String PACKAGE_JSON_TYPE_PROPERTY_NAME = "type";
public static final String PACKAGE_JSON_MODULE_VALUE = "module";
private static final String[] CORE_MODULES = new String[]{"assert", "async_hooks", "buffer", "child_process", "cluster", "crypto",
"dgram", "dns", "domain", "events", "fs", "http", "http2", "https", "module", "net",
"os", "path", "perf_hooks", "punycode", "querystring", "readline", "repl",
"stream", "string_decoder", "tls", "trace_events", "tty", "url", "util",
"v8", "vm", "worker_threads", "zlib"};
private CommonJSResolution() {
}
static boolean isCoreModule(String moduleIdentifier) {
return Arrays.asList(CORE_MODULES).contains(moduleIdentifier);
}
static String getCurrentFileNameFromStack() {
FrameInstance callerFrame = Truffle.getRuntime().getCallerFrame();
if (callerFrame != null) {
SourceSection encapsulatingSourceSection = null;
if (callerFrame.getCallNode() != null) {
encapsulatingSourceSection = callerFrame.getCallNode().getEncapsulatingSourceSection();
} else {
RootNode frameRootNode = JSFunction.getFrameRootNode(callerFrame);
if (frameRootNode != null) {
encapsulatingSourceSection = frameRootNode.getEncapsulatingSourceSection();
}
}
if (encapsulatingSourceSection != null && encapsulatingSourceSection.getSource() != null) {
Source source = encapsulatingSourceSection.getSource();
return source.getPath();
}
}
return null;
}
@CompilerDirectives.TruffleBoundary
static TruffleFile resolve(JSContext context, String moduleIdentifier, TruffleFile entryPath) {
if ("".equals(moduleIdentifier)) {
return null;
}
TruffleLanguage.Env env = context.getRealm().getEnv();
TruffleFile currentWorkingPath = entryPath;
if (moduleIdentifier.charAt(0) == '/') {
currentWorkingPath = getFileSystemRootPath(env);
}
if (isPathFileName(moduleIdentifier)) {
TruffleFile module = loadAsFileOrDirectory(context, env, joinPaths(env, currentWorkingPath, moduleIdentifier));
if (module != null) {
return module;
}
}
return loadNodeModulesOrSelfReference(context, env, moduleIdentifier, currentWorkingPath);
}
private static TruffleFile loadNodeModulesOrSelfReference(JSContext cx, TruffleLanguage.Env env, String moduleIdentifier, TruffleFile startFolder) {
List<TruffleFile> nodeModulesPaths = getNodeModulesPaths(startFolder);
for (TruffleFile s : nodeModulesPaths) {
TruffleFile module = loadAsFileOrDirectory(cx, env, joinPaths(env, s, moduleIdentifier));
if (module != null) {
return module;
}
}
return null;
}
public static TruffleFile loadIndex(TruffleLanguage.Env env, TruffleFile modulePath) {
TruffleFile indexJs = joinPaths(env, modulePath, INDEX_JS);
if (fileExists(indexJs)) {
return indexJs;
}
TruffleFile indexJson = joinPaths(env, modulePath, INDEX_JSON);
if (fileExists(indexJson)) {
return indexJson;
} else if (fileExists(joinPaths(env, modulePath, INDEX_NODE))) {
return null;
}
return null;
}
static TruffleFile loadAsFile(TruffleLanguage.Env env, TruffleFile modulePath) {
if (fileExists(modulePath)) {
return modulePath;
}
TruffleFile moduleJs = env.getPublicTruffleFile(modulePath.toString() + JS_EXT);
if (fileExists(moduleJs)) {
return moduleJs;
}
TruffleFile moduleJson = env.getPublicTruffleFile(modulePath.toString() + JSON_EXT);
if (fileExists(moduleJson)) {
return moduleJson;
}
if (fileExists(env.getPublicTruffleFile(modulePath.toString() + NODE_EXT))) {
return null;
}
return null;
}
public static List<TruffleFile> getNodeModulesPaths(TruffleFile path) {
List<TruffleFile> list = new ArrayList<>();
List<TruffleFile> paths = getAllParentPaths(path);
for (TruffleFile p : paths) {
if (p.endsWith(NODE_MODULES)) {
list.add(p);
} else {
TruffleFile truffleFile = p.resolve(NODE_MODULES);
list.add(truffleFile);
}
}
return list;
}
private static TruffleFile loadAsFileOrDirectory(JSContext cx, TruffleLanguage.Env env, TruffleFile modulePath) {
TruffleFile maybeFile = loadAsFile(env, modulePath);
if (maybeFile == null) {
return loadAsDirectory(cx, env, modulePath);
} else {
return maybeFile;
}
}
private static List<TruffleFile> getAllParentPaths(TruffleFile from) {
List<TruffleFile> paths = new ArrayList<>();
TruffleFile p = from;
while (p != null) {
paths.add(p);
p = p.getParent();
}
return paths;
}
private static TruffleFile loadAsDirectory(JSContext cx, TruffleLanguage.Env env, TruffleFile modulePath) {
TruffleFile packageJson = joinPaths(env, modulePath, PACKAGE_JSON);
if (fileExists(packageJson)) {
DynamicObject jsonObj = loadJsonObject(packageJson, cx);
if (JSDynamicObject.isJSDynamicObject(jsonObj)) {
Object main = JSObject.get(jsonObj, PACKAGE_JSON_MAIN_PROPERTY_NAME);
if (!JSRuntime.isString(main)) {
return loadIndex(env, modulePath);
}
TruffleFile module = joinPaths(env, modulePath, JSRuntime.safeToString(main));
TruffleFile asFile = loadAsFile(env, module);
if (asFile != null) {
return asFile;
} else {
return loadIndex(env, module);
}
}
} else {
return loadIndex(env, modulePath);
}
return null;
}
public static DynamicObject loadJsonObject(TruffleFile jsonFile, JSContext context) {
try {
if (fileExists(jsonFile)) {
Source source = null;
JSRealm realm = context.getRealm();
TruffleFile file = GlobalBuiltins.resolveRelativeFilePath(jsonFile.toString(), realm.getEnv());
if (file.isRegularFile()) {
source = sourceFromTruffleFile(file);
}
if (source == null) {
return null;
}
DynamicObject parse = (DynamicObject) realm.getJsonParseFunctionObject();
String jsonString = source.getCharacters().toString();
Object jsonObj = JSFunction.call(JSArguments.create(parse, parse, jsonString));
if (JSDynamicObject.isJSDynamicObject(jsonObj)) {
return (DynamicObject) jsonObj;
}
}
return null;
} catch (SecurityException e) {
throw Errors.createErrorFromException(e);
}
}
private static Source sourceFromTruffleFile(TruffleFile file) {
try {
return Source.newBuilder(JavaScriptLanguage.ID, file).build();
} catch (IOException | SecurityException e) {
return null;
}
}
public static boolean fileExists(TruffleFile modulePath) {
return modulePath.exists() && modulePath.isRegularFile();
}
private static boolean isPathFileName(String moduleIdentifier) {
return moduleIdentifier.startsWith("/") || moduleIdentifier.startsWith("./") || moduleIdentifier.startsWith("../");
}
public static TruffleFile joinPaths(TruffleLanguage.Env env, TruffleFile p1, String p2) {
Objects.requireNonNull(p1);
String pathSeparator = env.getFileNameSeparator();
String pathName = p1.normalize().toString();
TruffleFile truffleFile = env.getPublicTruffleFile(pathName + pathSeparator + p2);
return truffleFile.normalize();
}
private static TruffleFile getFileSystemRootPath(TruffleLanguage.Env env) {
TruffleFile root = env.getCurrentWorkingDirectory();
TruffleFile last = root;
while (root != null) {
last = root;
root = root.getParent();
}
return last;
}
}