diff options
28 files changed, 2914 insertions, 594 deletions
diff --git a/examples/macros/components/example.jsx b/examples/macros/components/example.jsx new file mode 100644 index 000000000..ad80ce9e1 --- /dev/null +++ b/examples/macros/components/example.jsx @@ -0,0 +1,17 @@ +// source code +import { matchInFile } from "macro:matchInFile"; + +export const IPAddresses = () => ( + <div> + <h2>recent ip addresses</h2> + <div className="Lines"> + {matchInFile("access.log", /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}/).map( + (ipAddress, index) => ( + <div className="Line" key={index}> + {ipAddress} + </div> + ) + )} + </div> + </div> +); diff --git a/examples/macros/components/index.tsx b/examples/macros/components/index.tsx new file mode 100644 index 000000000..6c3e39be7 --- /dev/null +++ b/examples/macros/components/index.tsx @@ -0,0 +1,11 @@ +import * as ReactDOM from "react-dom"; +import * as React from "react"; +import { IPAddresses } from "./example"; + +const Start = function () { + const root = document.createElement("div"); + document.body.appendChild(root); + ReactDOM.render(<IPAddresses />, root); +}; + +Start(); diff --git a/examples/macros/example.js b/examples/macros/example.js new file mode 100644 index 000000000..d612c1fa4 --- /dev/null +++ b/examples/macros/example.js @@ -0,0 +1,4 @@ +// source code +import { mysteryBox } from "macro:./mystery-box"; + +export default "You roll! " + mysteryBox(123); diff --git a/examples/macros/matchInFile.tsx b/examples/macros/matchInFile.tsx new file mode 100644 index 000000000..a73f7ee8b --- /dev/null +++ b/examples/macros/matchInFile.tsx @@ -0,0 +1,20 @@ +// macro code +export function matchInFile(callExpression) { + const [filePathNode, matcherNode] = callExpression.arguments; + const filePath: string = filePathNode.get(); + const matcher: RegExp = matcherNode.get(); + const file: string = Bun.readFile(Bun.cwd + filePath); + + return ( + <array> + {file + .split("\n") + .map((line) => line.match(matcher)) + .filter(Boolean) + .reverse() + .map((line) => ( + <string value={line[0]} /> + ))} + </array> + ); +} diff --git a/examples/macros/now.tsx b/examples/macros/now.tsx new file mode 100644 index 000000000..d5a9e7912 --- /dev/null +++ b/examples/macros/now.tsx @@ -0,0 +1,10 @@ +import moment from "moment"; +export function now(node) { + var fmt = "HH:mm:ss"; + const args = node.arguments; + if (args[0] instanceof <string />) { + fmt = args[0].get(); + } + const time = moment().format(fmt); + return <string value={time}></string>; +} diff --git a/examples/macros/package.json b/examples/macros/package.json new file mode 100644 index 000000000..de4e26453 --- /dev/null +++ b/examples/macros/package.json @@ -0,0 +1,12 @@ +{ + "name": "macros", + "version": "1.0.0", + "main": "index.js", + "license": "MIT", + "dependencies": { + "moment": "^2.29.1", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "react-refresh": "^0.10.0" + } +} diff --git a/examples/macros/public/index.html b/examples/macros/public/index.html new file mode 100644 index 000000000..a2a985b72 --- /dev/null +++ b/examples/macros/public/index.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html> + <head> + <title>Macro test</title> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + + <link rel="stylesheet" href="/styles.css" type="text/css" /> + </head> + + <body> + <script async type="module" src="/src/index.tsx"></script> + </body> +</html> diff --git a/examples/macros/styles.css b/examples/macros/styles.css new file mode 100644 index 000000000..87f0ef1a8 --- /dev/null +++ b/examples/macros/styles.css @@ -0,0 +1,26 @@ +html { + font-size: 4rem; + margin: 0; + padding: 0; + background-color: black; + + color: rgb(0, 255, 0); + font-family: "Courier"; +} + +body { + margin: 48px auto; + text-align: center; +} + +.Line { + font-size: 0.5rem; + font-family: monospace; +} + +.Lines { + text-align: left; + margin: 0 auto; + max-width: fit-content; + line-height: 1.5; +} diff --git a/examples/macros/tsconfig.json b/examples/macros/tsconfig.json new file mode 100644 index 000000000..4a98f5cad --- /dev/null +++ b/examples/macros/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": {}, + "jsx": "preserve" + } +} diff --git a/package.json b/package.json index 6a5e8d71c..2052081df 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,10 @@ { "dependencies": { + "moment": "^2.29.1", "peechy": "^0.4.18", - "puppeteer": "^10.2.0" + "puppeteer": "^10.2.0", + "react": "^17.0.2", + "react-dom": "^17.0.2" }, "scripts": { "build-runtime": "esbuild --target=esnext --bundle src/runtime/index.ts --format=iife --platform=browser --global-name=BUN_RUNTIME > src/runtime.out.js; cat src/runtime.footer.js >> src/runtime.out.js", diff --git a/src/ast/ast.js b/src/ast/ast.js deleted file mode 100644 index 06be333c5..000000000 --- a/src/ast/ast.js +++ /dev/null @@ -1,106 +0,0 @@ -globalThis.BunASTNode ??= class BunASTNode { - position = -1; -}; - -if (!globalThis.BunAST) { - globalThis.BunAST = { - EArray: class EArray extends BunASTNode { - #ptr = Number.MAX_SAFE_INTEGER; - }, - EUnary: class EUnary extends BunASTNode { - #ptr = Number.MAX_SAFE_INTEGER; - }, - EBinary: class EBinary extends BunASTNode { - #ptr = Number.MAX_SAFE_INTEGER; - }, - EClass: class EClass extends BunASTNode { - #ptr = Number.MAX_SAFE_INTEGER; - }, - ENew: class ENew extends BunASTNode { - #ptr = Number.MAX_SAFE_INTEGER; - }, - EFunction: class EFunction extends BunASTNode { - #ptr = Number.MAX_SAFE_INTEGER; - }, - ECall: class ECall extends BunASTNode { - #ptr = Number.MAX_SAFE_INTEGER; - }, - EDot: class EDot extends BunASTNode { - #ptr = Number.MAX_SAFE_INTEGER; - }, - EIndex: class EIndex extends BunASTNode { - #ptr = Number.MAX_SAFE_INTEGER; - }, - EArrow: class EArrow extends BunASTNode { - #ptr = Number.MAX_SAFE_INTEGER; - }, - EIdentifier: class EIdentifier extends BunASTNode { - #ptr = Number.MAX_SAFE_INTEGER; - }, - EImportIdentifier: class EImportIdentifier extends BunASTNode { - #ptr = Number.MAX_SAFE_INTEGER; - }, - EPrivateIdentifier: class EPrivateIdentifier extends BunASTNode { - #ptr = Number.MAX_SAFE_INTEGER; - }, - EJsxElement: class EJsxElement extends BunASTNode { - #ptr = Number.MAX_SAFE_INTEGER; - }, - EObject: class EObject extends BunASTNode { - #ptr = Number.MAX_SAFE_INTEGER; - }, - ESpread: class ESpread extends BunASTNode { - #ptr = Number.MAX_SAFE_INTEGER; - }, - ETemplatePart: class ETemplatePart extends BunASTNode { - #ptr = Number.MAX_SAFE_INTEGER; - }, - ETemplate: class ETemplate extends BunASTNode { - #ptr = Number.MAX_SAFE_INTEGER; - }, - ERegExp: class ERegExp extends BunASTNode { - #ptr = Number.MAX_SAFE_INTEGER; - }, - EAwait: class EAwait extends BunASTNode { - #ptr = Number.MAX_SAFE_INTEGER; - }, - EYield: class EYield extends BunASTNode { - #ptr = Number.MAX_SAFE_INTEGER; - }, - EIf: class EIf extends BunASTNode { - no = Number.MAX_SAFE_INTEGER; - yes = Number.MAX_SAFE_INTEGER; - }, - ERequire: class ERequire extends BunASTNode { - #ptr = Number.MAX_SAFE_INTEGER; - }, - EImport: class EImport extends BunASTNode { - #ptr = Number.MAX_SAFE_INTEGER; - }, - EBoolean: class EBoolean extends BunASTNode { - val = false; - }, - ENumber: class ENumber extends BunASTNode { - val = 0; - }, - EBigInt: class EBigInt extends BunASTNode { - #ptr = Number.MAX_SAFE_INTEGER; - }, - EString: class EString extends BunASTNode { - #ptr = Number.MAX_SAFE_INTEGER; - }, - EMissing: class EMissing extends BunASTNode { - #ptr = Number.MAX_SAFE_INTEGER; - }, - EThis: class EThis extends BunASTNode {}, - ESuper: class ESuper extends BunASTNode { - #ptr = Number.MAX_SAFE_INTEGER; - }, - ENull: class ENull extends BunASTNode {}, - EUndefined: class EUndefined extends BunASTNode {}, - ENewTarget: class ENewTarget extends BunASTNode { - #ptr = Number.MAX_SAFE_INTEGER; - }, - EImportMeta: class EImportMeta extends BunASTNode {}, - }; -} diff --git a/src/ast/ast.ts b/src/ast/ast.ts new file mode 100644 index 000000000..2ab25644d --- /dev/null +++ b/src/ast/ast.ts @@ -0,0 +1,153 @@ +class BunASTNode { + position = -1; +} +globalThis.BunASTNode = BunASTNode; +// hint to JS engine to store it as a f64 +const NullPtrValue = Number.MAX_SAFE_INTEGER; +const bindings = globalThis.BunASTBindings; + +const BunAST = { + EArray: class EArray extends BunASTNode { + #ptr = NullPtrValue; + }, + EUnary: class EUnary extends BunASTNode { + #ptr = NullPtrValue; + }, + EBinary: class EBinary extends BunASTNode { + #ptr = NullPtrValue; + }, + EClass: class EClass extends BunASTNode { + #ptr = NullPtrValue; + }, + ENew: class ENew extends BunASTNode { + #ptr = NullPtrValue; + }, + EFunction: class EFunction extends BunASTNode { + #ptr = NullPtrValue; + }, + ECall: class ECall extends BunASTNode { + #ptr = NullPtrValue; + }, + EDot: class EDot extends BunASTNode { + #ptr = NullPtrValue; + }, + EIndex: class EIndex extends BunASTNode { + #ptr = NullPtrValue; + }, + EArrow: class EArrow extends BunASTNode { + #ptr = NullPtrValue; + }, + EIdentifier: class EIdentifier extends BunASTNode { + #ptr = NullPtrValue; + }, + EImportIdentifier: class EImportIdentifier extends BunASTNode { + #ptr = NullPtrValue; + }, + EPrivateIdentifier: class EPrivateIdentifier extends BunASTNode { + #ptr = NullPtrValue; + }, + EJsxElement: class EJsxElement extends BunASTNode { + #ptr = NullPtrValue; + }, + EObject: class EObject extends BunASTNode { + #ptr = NullPtrValue; + }, + ESpread: class ESpread extends BunASTNode { + #ptr = NullPtrValue; + }, + ETemplatePart: class ETemplatePart extends BunASTNode { + #ptr = NullPtrValue; + }, + ETemplate: class ETemplate extends BunASTNode { + #ptr = NullPtrValue; + }, + ERegExp: class ERegExp extends BunASTNode { + #ptr = NullPtrValue; + }, + EAwait: class EAwait extends BunASTNode { + #ptr = NullPtrValue; + }, + EYield: class EYield extends BunASTNode { + #ptr = NullPtrValue; + }, + EIf: class EIf extends BunASTNode { + no = NullPtrValue; + yes = NullPtrValue; + test = NullPtrValue; + }, + ERequire: class ERequire extends BunASTNode { + #ptr = NullPtrValue; + }, + EImport: class EImport extends BunASTNode { + #ptr = NullPtrValue; + }, + EBoolean: class EBoolean extends BunASTNode { + val = false; + }, + ENumber: class ENumber extends BunASTNode { + val = 0; + }, + EBigInt: class EBigInt extends BunASTNode { + #ptr = NullPtrValue; + }, + EString: class EString extends BunASTNode { + #ptr = NullPtrValue; + }, + EMissing: class EMissing extends BunASTNode { + #ptr = NullPtrValue; + }, + EThis: class EThis extends BunASTNode {}, + ESuper: class ESuper extends BunASTNode { + #ptr = NullPtrValue; + }, + ENull: class ENull extends BunASTNode {}, + EUndefined: class EUndefined extends BunASTNode {}, + ENewTarget: class ENewTarget extends BunASTNode { + #ptr = NullPtrValue; + }, + EImportMeta: class EImportMeta extends BunASTNode {}, + SImport: class SImport extends BunASTNode { + #ptr = NullPtrValue; + }, +}; +globalThis.BunAST = BunAST; +const bunTags = [ + BunAST.EArray, + BunAST.EUnary, + BunAST.EBinary, + BunAST.EClass, + BunAST.ENew, + BunAST.EFunction, + BunAST.ECall, + BunAST.EDot, + BunAST.EIndex, + BunAST.EArrow, + BunAST.EIdentifier, + BunAST.EImportIdentifier, + BunAST.EPrivateIdentifier, + BunAST.EJsxElement, + BunAST.EObject, + BunAST.ESpread, + BunAST.ETemplatePart, + BunAST.ETemplate, + BunAST.ERegExp, + BunAST.EAwait, + BunAST.EYield, + BunAST.EIf, + BunAST.ERequire, + BunAST.EImport, + BunAST.EBoolean, + BunAST.ENumber, + BunAST.EBigInt, + BunAST.EString, + BunAST.EMissing, + BunAST.EThis, + BunAST.ESuper, + BunAST.ENull, + BunAST.EUndefined, + BunAST.ENewTarget, + BunAST.EImportMeta, + BunAST.SImport, +]; +globalThis.bunTags = bunTags; + diff --git a/src/bundler.zig b/src/bundler.zig index d36795d52..64982c822 100644 --- a/src/bundler.zig +++ b/src/bundler.zig @@ -133,6 +133,7 @@ pub const Bundler = struct { to.log = try allocator.create(logger.Log); to.log.* = logger.Log.init(allocator); to.setLog(to.log); + to.macro_context = null; } pub fn setLog(this: *ThisBundler, log: *logger.Log) void { diff --git a/src/feature_flags.zig b/src/feature_flags.zig index 9aa809879..8087cd6ba 100644 --- a/src/feature_flags.zig +++ b/src/feature_flags.zig @@ -68,4 +68,7 @@ pub const CSSInJSImportBehavior = enum { pub const remote_inspector = false; pub const auto_import_buffer = false; -pub const is_macro_enabled = env.isDebug; +pub const is_macro_enabled = true; + +pub const force_macro = false; +pub const include_filename_in_jsx = false; diff --git a/src/javascript/jsc/base.zig b/src/javascript/jsc/base.zig index 36f00f59e..a5f2d336d 100644 --- a/src/javascript/jsc/base.zig +++ b/src/javascript/jsc/base.zig @@ -1539,7 +1539,7 @@ export fn MarkedArrayBuffer_deallocator(bytes_: *c_void, ctx_: *c_void) void { pub fn castObj(obj: js.JSObjectRef, comptime Type: type) *Type { return JSPrivateDataPtr.from(js.JSObjectGetPrivate(obj)).as(Type); } -const JSExpr = @import("../../js_ast.zig").Macro.JSExpr; +const JSNode = @import("../../js_ast.zig").Macro.JSNode; pub const JSPrivateDataPtr = TaggedPointerUnion(.{ ResolveError, @@ -1550,7 +1550,7 @@ pub const JSPrivateDataPtr = TaggedPointerUnion(.{ Headers, Body, Router, - JSExpr, + JSNode, }); pub inline fn GetJSPrivateData(comptime Type: type, ref: js.JSObjectRef) ?*Type { diff --git a/src/javascript/jsc/bindings/ZigGlobalObject.cpp b/src/javascript/jsc/bindings/ZigGlobalObject.cpp index b7ebe53d2..712d1cc63 100644 --- a/src/javascript/jsc/bindings/ZigGlobalObject.cpp +++ b/src/javascript/jsc/bindings/ZigGlobalObject.cpp @@ -80,17 +80,21 @@ using JSObject = JSC::JSObject; using JSNonFinalObject = JSC::JSNonFinalObject; namespace JSCastingHelpers = JSC::JSCastingHelpers; +bool has_loaded_jsc = false; + extern "C" JSC__JSGlobalObject *Zig__GlobalObject__create(JSClassRef *globalObjectClass, int count, void *console_client) { - JSC::Options::useSourceProviderCache() = true; - JSC::Options::useUnlinkedCodeBlockJettisoning() = false; - // JSC::Options::useTopLevelAwait() = true; - JSC::Options::exposeInternalModuleLoader() = true; - - std::set_terminate([]() { Zig__GlobalObject__onCrash(); }); - WTF::initializeMainThread(); - JSC::initialize(); + if (!has_loaded_jsc) { + JSC::Options::useSourceProviderCache() = true; + JSC::Options::useUnlinkedCodeBlockJettisoning() = false; + // JSC::Options::useTopLevelAwait() = true; + JSC::Options::exposeInternalModuleLoader() = true; + std::set_terminate([]() { Zig__GlobalObject__onCrash(); }); + WTF::initializeMainThread(); + JSC::initialize(); + has_loaded_jsc = true; + } // JSC::Options::useCodeCache() = false; @@ -104,7 +108,7 @@ extern "C" JSC__JSGlobalObject *Zig__GlobalObject__create(JSClassRef *globalObje JSC::JSLockHolder locker(vm); Zig::GlobalObject *globalObject = - Zig::GlobalObject::create(vm, Zig::GlobalObject::createStructure(vm, JSC::jsNull())); + Zig::GlobalObject::create(vm, Zig::GlobalObject::createStructure(vm, JSC::jsNull())); globalObject->setConsole(globalObject); if (count > 0) { globalObject->installAPIGlobals(globalObjectClass, count); } diff --git a/src/javascript/jsc/bindings/bindings.cpp b/src/javascript/jsc/bindings/bindings.cpp index 50e6978b5..d847a3121 100644 --- a/src/javascript/jsc/bindings/bindings.cpp +++ b/src/javascript/jsc/bindings/bindings.cpp @@ -10,6 +10,7 @@ #include <JavaScriptCore/Identifier.h> #include <JavaScriptCore/IteratorOperations.h> #include <JavaScriptCore/JSArray.h> +#include <JavaScriptCore/JSArrayInlines.h> #include <JavaScriptCore/JSCInlines.h> #include <JavaScriptCore/JSCallbackObject.h> #include <JavaScriptCore/JSClassRef.h> @@ -51,6 +52,12 @@ JSC__JSValue JSC__JSValue__createEmptyObject(JSC__JSGlobalObject *globalObject, JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), initialCapacity)); } +uint32_t JSC__JSValue__getLengthOfArray(JSC__JSValue value, JSC__JSGlobalObject *globalObject) { + JSC::JSValue jsValue = JSC::JSValue::decode(value); + JSC::JSObject *object = jsValue.toObject(globalObject); + return JSC::toLength(globalObject, object); +} + void JSC__JSObject__putRecord(JSC__JSObject *object, JSC__JSGlobalObject *global, ZigString *key, ZigString *values, size_t valuesLen) { auto scope = DECLARE_THROW_SCOPE(global->vm()); @@ -220,9 +227,9 @@ JSC__JSValue JSC__Exception__value(JSC__Exception *arg0) { // JSC__PropertyNameArray__next(JSC__PropertyNameArray* arg0, size_t arg1); // CPP_DECL void JSC__PropertyNameArray__release(JSC__PropertyNameArray* arg0); size_t JSC__JSObject__getArrayLength(JSC__JSObject *arg0) { return arg0->getArrayLength(); } -JSC__JSValue JSC__JSObject__getIndex(JSC__JSObject *arg0, JSC__JSGlobalObject *arg1, +JSC__JSValue JSC__JSObject__getIndex(JSC__JSValue jsValue, JSC__JSGlobalObject *arg1, uint32_t arg3) { - return JSC::JSValue::encode(arg0->getIndex(arg1, arg3)); + return JSC::JSValue::encode(JSC::JSValue::decode(jsValue).toObject(arg1)->getIndex(arg1, arg3)); } JSC__JSValue JSC__JSObject__getDirect(JSC__JSObject *arg0, JSC__JSGlobalObject *arg1, ZigString arg2) { diff --git a/src/javascript/jsc/bindings/bindings.zig b/src/javascript/jsc/bindings/bindings.zig index f9cc9bad3..3355fab44 100644 --- a/src/javascript/jsc/bindings/bindings.zig +++ b/src/javascript/jsc/bindings/bindings.zig @@ -41,7 +41,7 @@ pub const JSObject = extern struct { return create(global, length, creator, Type.call); } - pub fn getIndex(this: *JSObject, globalThis: *JSGlobalObject, i: u32) JSValue { + pub fn getIndex(this: JSValue, globalThis: *JSGlobalObject, i: u32) JSValue { return cppFn("getIndex", .{ this, globalThis, @@ -100,6 +100,10 @@ pub const ZigString = extern struct { return ZigString{ .ptr = slice_.ptr, .len = slice_.len }; } + pub inline fn toRef(slice_: []const u8, global: *JSGlobalObject) C_API.JSValueRef { + return init(slice_).toValue(global).asRef(); + } + pub const Empty = ZigString{ .ptr = "", .len = 0 }; pub fn slice(this: *const ZigString) []const u8 { @@ -1076,6 +1080,30 @@ pub const URL = extern struct { pub const Extern = [_][]const u8{ "fromFileSystemPath", "fromString", "isEmpty", "isValid", "protocol", "encodedUser", "encodedPassword", "host", "path", "lastPathComponent", "query", "fragmentIdentifier", "queryWithLeadingQuestionMark", "fragmentIdentifierWithLeadingNumberSign", "stringWithoutQueryOrFragmentIdentifier", "stringWithoutFragmentIdentifier", "protocolHostAndPort", "hostAndPort", "user", "password", "fileSystemPath", "setProtocol", "setHost", "setHostAndPort", "setUser", "setPassword", "setPath", "setQuery", "truncatedForUseAsBase" }; }; +pub const JSArrayIterator = struct { + i: u32 = 0, + len: u32 = 0, + array: JSValue, + global: *JSGlobalObject, + + pub fn init(value: JSValue, global: *JSGlobalObject) JSArrayIterator { + return .{ + .array = value, + .global = global, + .len = value.getLengthOfArray(global), + }; + } + + pub fn next(this: *JSArrayIterator) ?JSValue { + if (!(this.i < this.len)) { + return null; + } + const i = this.i; + this.i += 1; + return JSObject.getIndex(this.array, this.global, i); + } +}; + pub const String = extern struct { pub const shim = Shimmer("WTF", "String", @This()); bytes: shim.Bytes, @@ -1226,6 +1254,10 @@ pub const JSValue = enum(i64) { }); } + pub inline fn arrayIterator(this: JSValue, global: *JSGlobalObject) JSArrayIterator { + return JSArrayIterator.init(this, global); + } + pub fn jsNumberFromDouble(i: f64) JSValue { return cppFn("jsNumberFromDouble", .{i}); } @@ -1411,6 +1443,17 @@ pub const JSValue = enum(i64) { }); } + pub inline fn toU16(this: JSValue) u36 { + return @intCast(u16, this.toInt32()); + } + + pub fn getLengthOfArray(this: JSValue, globalThis: *JSGlobalObject) u32 { + return cppFn("getLengthOfArray", .{ + this, + globalThis, + }); + } + pub fn isAggregateError(this: JSValue, globalObject: *JSGlobalObject) bool { return cppFn("isAggregateError", .{ this, globalObject }); } @@ -1427,11 +1470,11 @@ pub const JSValue = enum(i64) { } pub inline fn asRef(this: JSValue) C_API.JSValueRef { - return @intToPtr(C_API.JSValueRef, @intCast(usize, @enumToInt(this))); + return @intToPtr(C_API.JSValueRef, @bitCast(usize, @enumToInt(this))); } pub inline fn fromRef(this: C_API.JSValueRef) JSValue { - return @intToEnum(JSValue, @intCast(i64, @ptrToInt(this))); + return @intToEnum(JSValue, @bitCast(i64, @ptrToInt(this))); } pub inline fn asObjectRef(this: JSValue) C_API.JSObjectRef { @@ -1442,7 +1485,7 @@ pub const JSValue = enum(i64) { return @intToPtr(*c_void, @intCast(usize, @enumToInt(this))); } - pub const Extern = [_][]const u8{ "toZigString", "createStringArray", "createEmptyObject", "putRecord", "asPromise", "isClass", "getNameProperty", "getClassName", "getErrorsProperty", "toInt32", "toBoolean", "isInt32", "isIterable", "forEach", "isAggregateError", "toZigException", "isException", "toWTFString", "hasProperty", "getPropertyNames", "getDirect", "putDirect", "get", "getIfExists", "asString", "asObject", "asNumber", "isError", "jsNull", "jsUndefined", "jsTDZValue", "jsBoolean", "jsDoubleNumber", "jsNumberFromDouble", "jsNumberFromChar", "jsNumberFromU16", "jsNumberFromInt32", "jsNumberFromInt64", "jsNumberFromUint64", "isUndefined", "isNull", "isUndefinedOrNull", "isBoolean", "isAnyInt", "isUInt32AsAnyInt", "isInt32AsAnyInt", "isNumber", "isString", "isBigInt", "isHeapBigInt", "isBigInt32", "isSymbol", "isPrimitive", "isGetterSetter", "isCustomGetterSetter", "isObject", "isCell", "asCell", "toString", "toStringOrNull", "toPropertyKey", "toPropertyKeyValue", "toObject", "toString", "getPrototype", "getPropertyByPropertyName", "eqlValue", "eqlCell", "isCallable" }; + pub const Extern = [_][]const u8{ "getLengthOfArray", "toZigString", "createStringArray", "createEmptyObject", "putRecord", "asPromise", "isClass", "getNameProperty", "getClassName", "getErrorsProperty", "toInt32", "toBoolean", "isInt32", "isIterable", "forEach", "isAggregateError", "toZigException", "isException", "toWTFString", "hasProperty", "getPropertyNames", "getDirect", "putDirect", "get", "getIfExists", "asString", "asObject", "asNumber", "isError", "jsNull", "jsUndefined", "jsTDZValue", "jsBoolean", "jsDoubleNumber", "jsNumberFromDouble", "jsNumberFromChar", "jsNumberFromU16", "jsNumberFromInt32", "jsNumberFromInt64", "jsNumberFromUint64", "isUndefined", "isNull", "isUndefinedOrNull", "isBoolean", "isAnyInt", "isUInt32AsAnyInt", "isInt32AsAnyInt", "isNumber", "isString", "isBigInt", "isHeapBigInt", "isBigInt32", "isSymbol", "isPrimitive", "isGetterSetter", "isCustomGetterSetter", "isObject", "isCell", "asCell", "toString", "toStringOrNull", "toPropertyKey", "toPropertyKeyValue", "toObject", "toString", "getPrototype", "getPropertyByPropertyName", "eqlValue", "eqlCell", "isCallable" }; }; pub const PropertyName = extern struct { diff --git a/src/javascript/jsc/bindings/headers-cpp.h b/src/javascript/jsc/bindings/headers-cpp.h index 7ea94d875..63510791c 100644 --- a/src/javascript/jsc/bindings/headers-cpp.h +++ b/src/javascript/jsc/bindings/headers-cpp.h @@ -1,4 +1,4 @@ -//-- AUTOGENERATED FILE -- 1631749917 +//-- AUTOGENERATED FILE -- 1632635195 // clang-format off #pragma once diff --git a/src/javascript/jsc/bindings/headers.h b/src/javascript/jsc/bindings/headers.h index e40651476..657d4a4f4 100644 --- a/src/javascript/jsc/bindings/headers.h +++ b/src/javascript/jsc/bindings/headers.h @@ -1,4 +1,4 @@ -//-- AUTOGENERATED FILE -- 1631749917 +//-- AUTOGENERATED FILE -- 1632635195 // clang-format: off #pragma once @@ -234,7 +234,7 @@ typedef void* JSClassRef; CPP_DECL JSC__JSValue JSC__JSObject__create(JSC__JSGlobalObject* arg0, size_t arg1, void* arg2, void (* ArgFn3)(void* arg0, JSC__JSObject* arg1, JSC__JSGlobalObject* arg2)); CPP_DECL size_t JSC__JSObject__getArrayLength(JSC__JSObject* arg0); CPP_DECL JSC__JSValue JSC__JSObject__getDirect(JSC__JSObject* arg0, JSC__JSGlobalObject* arg1, ZigString arg2); -CPP_DECL JSC__JSValue JSC__JSObject__getIndex(JSC__JSObject* arg0, JSC__JSGlobalObject* arg1, uint32_t arg2); +CPP_DECL JSC__JSValue JSC__JSObject__getIndex(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, uint32_t arg2); CPP_DECL void JSC__JSObject__putDirect(JSC__JSObject* arg0, JSC__JSGlobalObject* arg1, ZigString arg2, JSC__JSValue JSValue3); CPP_DECL void JSC__JSObject__putRecord(JSC__JSObject* arg0, JSC__JSGlobalObject* arg1, ZigString* arg2, ZigString* arg3, size_t arg4); CPP_DECL JSC__JSValue ZigString__toErrorInstance(const ZigString* arg0, JSC__JSGlobalObject* arg1); @@ -421,6 +421,7 @@ CPP_DECL bool JSC__JSValue__eqlValue(JSC__JSValue JSValue0, JSC__JSValue JSValue CPP_DECL void JSC__JSValue__forEach(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, void (* ArgFn2)(JSC__VM* arg0, JSC__JSGlobalObject* arg1, JSC__JSValue JSValue2)); CPP_DECL void JSC__JSValue__getClassName(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, ZigString* arg2); CPP_DECL JSC__JSValue JSC__JSValue__getErrorsProperty(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1); +CPP_DECL uint32_t JSC__JSValue__getLengthOfArray(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1); CPP_DECL void JSC__JSValue__getNameProperty(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, ZigString* arg2); CPP_DECL JSC__JSValue JSC__JSValue__getPrototype(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1); CPP_DECL bool JSC__JSValue__isAggregateError(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1); diff --git a/src/javascript/jsc/bindings/headers.zig b/src/javascript/jsc/bindings/headers.zig index 1b295fbad..a2c111d2d 100644 --- a/src/javascript/jsc/bindings/headers.zig +++ b/src/javascript/jsc/bindings/headers.zig @@ -37,69 +37,70 @@ pub const __mbstate_t = extern union { pub const __darwin_mbstate_t = __mbstate_t; pub const __darwin_ptrdiff_t = c_long; pub const __darwin_size_t = c_ulong; - + pub const JSC__RegExpPrototype = struct_JSC__RegExpPrototype; - + pub const JSC__GeneratorPrototype = struct_JSC__GeneratorPrototype; - + pub const JSC__ArrayIteratorPrototype = struct_JSC__ArrayIteratorPrototype; - + pub const JSC__StringPrototype = struct_JSC__StringPrototype; pub const WTF__StringView = bWTF__StringView; - + pub const JSC__JSPromisePrototype = struct_JSC__JSPromisePrototype; pub const JSC__CatchScope = bJSC__CatchScope; pub const JSC__ThrowScope = bJSC__ThrowScope; pub const JSC__PropertyName = bJSC__PropertyName; pub const JSC__JSObject = bJSC__JSObject; pub const WTF__ExternalStringImpl = bWTF__ExternalStringImpl; - + pub const JSC__AsyncIteratorPrototype = struct_JSC__AsyncIteratorPrototype; pub const WTF__StringImpl = bWTF__StringImpl; pub const JSC__JSLock = bJSC__JSLock; pub const JSC__JSModuleLoader = bJSC__JSModuleLoader; pub const JSC__VM = bJSC__VM; - + pub const JSC__AsyncGeneratorPrototype = struct_JSC__AsyncGeneratorPrototype; - + pub const JSC__AsyncGeneratorFunctionPrototype = struct_JSC__AsyncGeneratorFunctionPrototype; pub const JSC__JSGlobalObject = bJSC__JSGlobalObject; pub const JSC__JSFunction = bJSC__JSFunction; - + pub const JSC__ArrayPrototype = struct_JSC__ArrayPrototype; - + pub const JSC__AsyncFunctionPrototype = struct_JSC__AsyncFunctionPrototype; pub const JSC__Identifier = bJSC__Identifier; pub const JSC__JSPromise = bJSC__JSPromise; - + pub const JSC__SetIteratorPrototype = struct_JSC__SetIteratorPrototype; pub const JSC__SourceCode = bJSC__SourceCode; pub const JSC__JSCell = bJSC__JSCell; - + pub const JSC__BigIntPrototype = struct_JSC__BigIntPrototype; - + pub const JSC__GeneratorFunctionPrototype = struct_JSC__GeneratorFunctionPrototype; pub const JSC__SourceOrigin = bJSC__SourceOrigin; pub const JSC__JSModuleRecord = bJSC__JSModuleRecord; pub const WTF__String = bWTF__String; pub const WTF__URL = bWTF__URL; - + + pub const JSC__IteratorPrototype = struct_JSC__IteratorPrototype; pub const JSC__JSInternalPromise = bJSC__JSInternalPromise; - + pub const JSC__FunctionPrototype = struct_JSC__FunctionPrototype; pub const Inspector__ScriptArguments = bInspector__ScriptArguments; pub const JSC__Exception = bJSC__Exception; pub const JSC__JSString = bJSC__JSString; - + pub const JSC__ObjectPrototype = struct_JSC__ObjectPrototype; pub const JSC__CallFrame = bJSC__CallFrame; - + pub const JSC__MapIteratorPrototype = struct_JSC__MapIteratorPrototype; pub extern fn JSC__JSObject__create(arg0: [*c]JSC__JSGlobalObject, arg1: usize, arg2: ?*c_void, ArgFn3: ?fn (?*c_void, [*c]JSC__JSObject, [*c]JSC__JSGlobalObject) callconv(.C) void) JSC__JSValue; pub extern fn JSC__JSObject__getArrayLength(arg0: [*c]JSC__JSObject) usize; pub extern fn JSC__JSObject__getDirect(arg0: [*c]JSC__JSObject, arg1: [*c]JSC__JSGlobalObject, arg2: ZigString) JSC__JSValue; -pub extern fn JSC__JSObject__getIndex(arg0: [*c]JSC__JSObject, arg1: [*c]JSC__JSGlobalObject, arg2: u32) JSC__JSValue; +pub extern fn JSC__JSObject__getIndex(JSValue0: JSC__JSValue, arg1: [*c]JSC__JSGlobalObject, arg2: u32) JSC__JSValue; pub extern fn JSC__JSObject__putDirect(arg0: [*c]JSC__JSObject, arg1: [*c]JSC__JSGlobalObject, arg2: ZigString, JSValue3: JSC__JSValue) void; pub extern fn JSC__JSObject__putRecord(arg0: [*c]JSC__JSObject, arg1: [*c]JSC__JSGlobalObject, arg2: [*c]ZigString, arg3: [*c]ZigString, arg4: usize) void; pub extern fn ZigString__toErrorInstance(arg0: [*c]const ZigString, arg1: [*c]JSC__JSGlobalObject) JSC__JSValue; @@ -154,14 +155,14 @@ pub extern fn JSC__JSInternalPromise__then(arg0: [*c]JSC__JSInternalPromise, arg pub extern fn JSC__SourceOrigin__fromURL(arg0: [*c]const WTF__URL) bJSC__SourceOrigin; pub extern fn JSC__SourceCode__fromString(arg0: [*c]JSC__SourceCode, arg1: [*c]const WTF__String, arg2: [*c]const JSC__SourceOrigin, arg3: [*c]WTF__String, SourceType4: u8) void; pub extern fn JSC__JSFunction__calculatedDisplayName(arg0: [*c]JSC__JSFunction, arg1: [*c]JSC__VM) bWTF__String; -pub extern fn JSC__JSFunction__callWithArguments(JSValue0: JSC__JSValue, arg1: [*c]JSC__JSGlobalObject, arg2: [*c]JSC__JSValue, arg3: usize, arg4: *?*JSC__Exception, arg5: [*c]const u8) JSC__JSValue; -pub extern fn JSC__JSFunction__callWithArgumentsAndThis(JSValue0: JSC__JSValue, JSValue1: JSC__JSValue, arg2: [*c]JSC__JSGlobalObject, arg3: [*c]JSC__JSValue, arg4: usize, arg5: *?*JSC__Exception, arg6: [*c]const u8) JSC__JSValue; -pub extern fn JSC__JSFunction__callWithoutAnyArgumentsOrThis(JSValue0: JSC__JSValue, arg1: [*c]JSC__JSGlobalObject, arg2: *?*JSC__Exception, arg3: [*c]const u8) JSC__JSValue; -pub extern fn JSC__JSFunction__callWithThis(JSValue0: JSC__JSValue, arg1: [*c]JSC__JSGlobalObject, JSValue2: JSC__JSValue, arg3: *?*JSC__Exception, arg4: [*c]const u8) JSC__JSValue; -pub extern fn JSC__JSFunction__constructWithArguments(JSValue0: JSC__JSValue, arg1: [*c]JSC__JSGlobalObject, arg2: [*c]JSC__JSValue, arg3: usize, arg4: *?*JSC__Exception, arg5: [*c]const u8) JSC__JSValue; -pub extern fn JSC__JSFunction__constructWithArgumentsAndNewTarget(JSValue0: JSC__JSValue, JSValue1: JSC__JSValue, arg2: [*c]JSC__JSGlobalObject, arg3: [*c]JSC__JSValue, arg4: usize, arg5: *?*JSC__Exception, arg6: [*c]const u8) JSC__JSValue; -pub extern fn JSC__JSFunction__constructWithNewTarget(JSValue0: JSC__JSValue, arg1: [*c]JSC__JSGlobalObject, JSValue2: JSC__JSValue, arg3: *?*JSC__Exception, arg4: [*c]const u8) JSC__JSValue; -pub extern fn JSC__JSFunction__constructWithoutAnyArgumentsOrNewTarget(JSValue0: JSC__JSValue, arg1: [*c]JSC__JSGlobalObject, arg2: *?*JSC__Exception, arg3: [*c]const u8) JSC__JSValue; +pub extern fn JSC__JSFunction__callWithArguments(JSValue0: JSC__JSValue, arg1: [*c]JSC__JSGlobalObject, arg2: [*c]JSC__JSValue, arg3: usize, arg4: *?*JSC__Exception , arg5: [*c]const u8) JSC__JSValue; +pub extern fn JSC__JSFunction__callWithArgumentsAndThis(JSValue0: JSC__JSValue, JSValue1: JSC__JSValue, arg2: [*c]JSC__JSGlobalObject, arg3: [*c]JSC__JSValue, arg4: usize, arg5: *?*JSC__Exception , arg6: [*c]const u8) JSC__JSValue; +pub extern fn JSC__JSFunction__callWithoutAnyArgumentsOrThis(JSValue0: JSC__JSValue, arg1: [*c]JSC__JSGlobalObject, arg2: *?*JSC__Exception , arg3: [*c]const u8) JSC__JSValue; +pub extern fn JSC__JSFunction__callWithThis(JSValue0: JSC__JSValue, arg1: [*c]JSC__JSGlobalObject, JSValue2: JSC__JSValue, arg3: *?*JSC__Exception , arg4: [*c]const u8) JSC__JSValue; +pub extern fn JSC__JSFunction__constructWithArguments(JSValue0: JSC__JSValue, arg1: [*c]JSC__JSGlobalObject, arg2: [*c]JSC__JSValue, arg3: usize, arg4: *?*JSC__Exception , arg5: [*c]const u8) JSC__JSValue; +pub extern fn JSC__JSFunction__constructWithArgumentsAndNewTarget(JSValue0: JSC__JSValue, JSValue1: JSC__JSValue, arg2: [*c]JSC__JSGlobalObject, arg3: [*c]JSC__JSValue, arg4: usize, arg5: *?*JSC__Exception , arg6: [*c]const u8) JSC__JSValue; +pub extern fn JSC__JSFunction__constructWithNewTarget(JSValue0: JSC__JSValue, arg1: [*c]JSC__JSGlobalObject, JSValue2: JSC__JSValue, arg3: *?*JSC__Exception , arg4: [*c]const u8) JSC__JSValue; +pub extern fn JSC__JSFunction__constructWithoutAnyArgumentsOrNewTarget(JSValue0: JSC__JSValue, arg1: [*c]JSC__JSGlobalObject, arg2: *?*JSC__Exception , arg3: [*c]const u8) JSC__JSValue; pub extern fn JSC__JSFunction__createFromNative(arg0: [*c]JSC__JSGlobalObject, arg1: u16, arg2: [*c]const WTF__String, arg3: ?*c_void, ArgFn4: ?fn (?*c_void, [*c]JSC__JSGlobalObject, [*c]JSC__CallFrame) callconv(.C) JSC__JSValue) [*c]JSC__JSFunction; pub extern fn JSC__JSFunction__displayName(arg0: [*c]JSC__JSFunction, arg1: [*c]JSC__VM) bWTF__String; pub extern fn JSC__JSFunction__getName(arg0: [*c]JSC__JSFunction, arg1: [*c]JSC__VM) bWTF__String; @@ -244,6 +245,7 @@ pub extern fn JSC__JSValue__eqlValue(JSValue0: JSC__JSValue, JSValue1: JSC__JSVa pub extern fn JSC__JSValue__forEach(JSValue0: JSC__JSValue, arg1: [*c]JSC__JSGlobalObject, ArgFn2: ?fn ([*c]JSC__VM, [*c]JSC__JSGlobalObject, JSC__JSValue) callconv(.C) void) void; pub extern fn JSC__JSValue__getClassName(JSValue0: JSC__JSValue, arg1: [*c]JSC__JSGlobalObject, arg2: [*c]ZigString) void; pub extern fn JSC__JSValue__getErrorsProperty(JSValue0: JSC__JSValue, arg1: [*c]JSC__JSGlobalObject) JSC__JSValue; +pub extern fn JSC__JSValue__getLengthOfArray(JSValue0: JSC__JSValue, arg1: [*c]JSC__JSGlobalObject) u32; pub extern fn JSC__JSValue__getNameProperty(JSValue0: JSC__JSValue, arg1: [*c]JSC__JSGlobalObject, arg2: [*c]ZigString) void; pub extern fn JSC__JSValue__getPrototype(JSValue0: JSC__JSValue, arg1: [*c]JSC__JSGlobalObject) JSC__JSValue; pub extern fn JSC__JSValue__isAggregateError(JSValue0: JSC__JSValue, arg1: [*c]JSC__JSGlobalObject) bool; diff --git a/src/javascript/jsc/javascript.zig b/src/javascript/jsc/javascript.zig index 8d7626afb..9b1d22a05 100644 --- a/src/javascript/jsc/javascript.zig +++ b/src/javascript/jsc/javascript.zig @@ -36,6 +36,7 @@ pub const GlobalClasses = [_]type{ ResolveError.Class, Bun.Class, Fetch.Class, + js_ast.Macro.JSNode.BunJSXCallbackFunction, }; const Blob = @import("../../blob.zig"); @@ -178,6 +179,74 @@ pub const Bun = struct { return ZigString.init(VirtualMachine.vm.bundler.options.routes.dir).toValue(VirtualMachine.vm.global).asRef(); } + pub fn getFilePath(ctx: js.JSContextRef, arguments: []const js.JSValueRef, buf: []u8, exception: js.ExceptionRef) ?string { + if (arguments.len != 1) { + JSError(getAllocator(ctx), "Expected a file path as a string or an array of strings to be part of a file path.", .{}, ctx, exception); + return null; + } + + const value = arguments[0]; + if (js.JSValueIsString(ctx, value)) { + var out = ZigString.Empty; + JSValue.toZigString(JSValue.fromRef(value), &out, VirtualMachine.vm.global); + var out_slice = out.slice(); + + // The dots are kind of unnecessary. They'll be normalized. + if (out.len == 0 or @ptrToInt(out.ptr) == 0 or std.mem.eql(u8, out_slice, ".") or std.mem.eql(u8, out_slice, "..") or std.mem.eql(u8, out_slice, "../")) { + JSError(getAllocator(ctx), "Expected a file path as a string or an array of strings to be part of a file path.", .{}, ctx, exception); + return null; + } + + var parts = [_]string{out_slice}; + // This does the equivalent of Node's path.normalize(path.join(cwd, out_slice)) + var res = VirtualMachine.vm.bundler.fs.absBuf(&parts, buf); + + return res; + } else if (js.JSValueIsArray(ctx, value)) { + var temp_strings_list: [32]string = undefined; + var temp_strings_list_len: u8 = 0; + defer { + for (temp_strings_list[0..temp_strings_list_len]) |_, i| { + temp_strings_list[i] = ""; + } + } + + var iter = JSValue.fromRef(value).arrayIterator(VirtualMachine.vm.global); + while (iter.next()) |item| { + if (temp_strings_list_len >= temp_strings_list.len) { + break; + } + + if (!item.isString()) { + JSError(getAllocator(ctx), "Expected a file path as a string or an array of strings to be part of a file path.", .{}, ctx, exception); + return null; + } + + var out = ZigString.Empty; + JSValue.toZigString(item, &out, VirtualMachine.vm.global); + const out_slice = out.slice(); + + temp_strings_list[temp_strings_list_len] = out_slice; + // The dots are kind of unnecessary. They'll be normalized. + if (out.len == 0 or @ptrToInt(out.ptr) == 0 or std.mem.eql(u8, out_slice, ".") or std.mem.eql(u8, out_slice, "..") or std.mem.eql(u8, out_slice, "../")) { + JSError(getAllocator(ctx), "Expected a file path as a string or an array of strings to be part of a file path.", .{}, ctx, exception); + return null; + } + temp_strings_list_len += 1; + } + + if (temp_strings_list_len == 0) { + JSError(getAllocator(ctx), "Expected a file path as a string or an array of strings to be part of a file path.", .{}, ctx, exception); + return null; + } + + return VirtualMachine.vm.bundler.fs.absBuf(temp_strings_list[0..temp_strings_list_len], buf); + } else { + JSError(getAllocator(ctx), "Expected a file path as a string or an array of strings to be part of a file path.", .{}, ctx, exception); + return null; + } + } + pub fn getImportedStyles( this: void, ctx: js.JSContextRef, @@ -195,59 +264,52 @@ pub const Bun = struct { return JSValue.createStringArray(VirtualMachine.vm.global, styles.ptr, styles.len).asRef(); } - pub fn getRouteFiles( - this: void, + pub fn readFileAsStringCallback( ctx: js.JSContextRef, - function: js.JSObjectRef, - thisObject: js.JSObjectRef, - arguments: []const js.JSValueRef, + buf_z: [:0]const u8, exception: js.ExceptionRef, ) js.JSValueRef { - if (VirtualMachine.vm.bundler.router == null) return js.JSValueMakeNull(ctx); - - const router = &(VirtualMachine.vm.bundler.router orelse unreachable); - const list = router.getEntryPointsWithBuffer(VirtualMachine.vm.allocator, false) catch unreachable; - VirtualMachine.vm.flush_list.append(list.buffer) catch {}; - defer VirtualMachine.vm.allocator.free(list.entry_points); + const path = buf_z.ptr[0..buf_z.len]; + var file = std.fs.cwd().openFileZ(buf_z, .{ .read = true, .write = false }) catch |err| { + JSError(getAllocator(ctx), "Opening file {s} for path: \"{s}\"", .{ @errorName(err), path }, ctx, exception); + return js.JSValueMakeUndefined(ctx); + }; - for (routes_list_strings[0..std.math.min(list.entry_points.len, routes_list_strings.len)]) |_, i| { - routes_list_strings[i] = ZigString.init(list.entry_points[i]); - } + defer file.close(); - const ref = JSValue.createStringArray(VirtualMachine.vm.global, &routes_list_strings, list.entry_points.len).asRef(); - return ref; - } + const stat = file.stat() catch |err| { + JSError(getAllocator(ctx), "Getting file size {s} for \"{s}\"", .{ @errorName(err), path }, ctx, exception); + return js.JSValueMakeUndefined(ctx); + }; - pub fn readFileAsBytes( - this: void, - ctx: js.JSContextRef, - function: js.JSObjectRef, - thisObject: js.JSObjectRef, - arguments: []const js.JSValueRef, - exception: js.ExceptionRef, - ) js.JSValueRef { - if (arguments.len != 1 or !JSValue.isString(JSValue.fromRef(arguments[0]))) { - JSError(getAllocator(ctx), "readFileBytes expects a file path as a string. e.g. Bun.readFile(\"public/index.html\")", .{}, ctx, exception); + if (stat.kind != .File) { + JSError(getAllocator(ctx), "Can't read a {s} as a string (\"{s}\")", .{ @tagName(stat.kind), path }, ctx, exception); return js.JSValueMakeUndefined(ctx); } - var out = ZigString.Empty; - JSValue.toZigString(JSValue.fromRef(arguments[0]), &out, VirtualMachine.vm.global); - var out_slice = out.slice(); - // The dots are kind of unnecessary. They'll be normalized. - if (out.len == 0 or @ptrToInt(out.ptr) == 0 or std.mem.eql(u8, out_slice, ".") or std.mem.eql(u8, out_slice, "..") or std.mem.eql(u8, out_slice, "../")) { - JSError(getAllocator(ctx), "readFileBytes expects a valid file path. e.g. Bun.readFile(\"public/index.html\")", .{}, ctx, exception); + var contents_buf = VirtualMachine.vm.allocator.alloc(u8, stat.size + 2) catch unreachable; // OOM + defer VirtualMachine.vm.allocator.free(contents_buf); + const contents_len = file.readAll(contents_buf) catch |err| { + JSError(getAllocator(ctx), "{s} reading file (\"{s}\")", .{ @errorName(err), path }, ctx, exception); return js.JSValueMakeUndefined(ctx); - } + }; - var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; + contents_buf[contents_len] = 0; - var parts = [_]string{out_slice}; - // This does the equivalent of Node's path.normalize(path.join(cwd, out_slice)) - var path = VirtualMachine.vm.bundler.fs.absBuf(&parts, &buf); - buf[path.len] = 0; + // Very slow to do it this way. We're copying the string twice. + // But it's important that this string is garbage collected instead of manually managed. + // We can't really recycle this one. + // TODO: use external string + return js.JSValueMakeString(ctx, js.JSStringCreateWithUTF8CString(contents_buf.ptr)); + } + + pub fn readFileAsBytesCallback( + ctx: js.JSContextRef, + buf_z: [:0]const u8, + exception: js.ExceptionRef, + ) js.JSValueRef { + const path = buf_z.ptr[0..buf_z.len]; - const buf_z: [:0]const u8 = buf[0..path.len :0]; var file = std.fs.cwd().openFileZ(buf_z, .{ .read = true, .write = false }) catch |err| { JSError(getAllocator(ctx), "Opening file {s} for path: \"{s}\"", .{ @errorName(err), path }, ctx, exception); return js.JSValueMakeUndefined(ctx); @@ -284,7 +346,7 @@ pub const Bun = struct { return marked_array_buffer.toJSObjectRef(ctx, exception); } - pub fn readFileAsString( + pub fn getRouteFiles( this: void, ctx: js.JSContextRef, function: js.JSObjectRef, @@ -292,59 +354,53 @@ pub const Bun = struct { arguments: []const js.JSValueRef, exception: js.ExceptionRef, ) js.JSValueRef { - if (arguments.len != 1 or !JSValue.isString(JSValue.fromRef(arguments[0]))) { - JSError(getAllocator(ctx), "readFile expects a file path as a string. e.g. Bun.readFile(\"public/index.html\")", .{}, ctx, exception); - return js.JSValueMakeUndefined(ctx); - } - var out = ZigString.Empty; - JSValue.toZigString(JSValue.fromRef(arguments[0]), &out, VirtualMachine.vm.global); - var out_slice = out.slice(); + if (VirtualMachine.vm.bundler.router == null) return js.JSValueMakeNull(ctx); - // The dots are kind of unnecessary. They'll be normalized. - if (out.len == 0 or @ptrToInt(out.ptr) == 0 or std.mem.eql(u8, out_slice, ".") or std.mem.eql(u8, out_slice, "..") or std.mem.eql(u8, out_slice, "../")) { - JSError(getAllocator(ctx), "readFile expects a valid file path. e.g. Bun.readFile(\"public/index.html\")", .{}, ctx, exception); - return js.JSValueMakeUndefined(ctx); + const router = &(VirtualMachine.vm.bundler.router orelse unreachable); + const list = router.getEntryPointsWithBuffer(VirtualMachine.vm.allocator, false) catch unreachable; + VirtualMachine.vm.flush_list.append(list.buffer) catch {}; + defer VirtualMachine.vm.allocator.free(list.entry_points); + + for (routes_list_strings[0..std.math.min(list.entry_points.len, routes_list_strings.len)]) |_, i| { + routes_list_strings[i] = ZigString.init(list.entry_points[i]); } - var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; + const ref = JSValue.createStringArray(VirtualMachine.vm.global, &routes_list_strings, list.entry_points.len).asRef(); + return ref; + } - var parts = [_]string{out_slice}; - // This does the equivalent of Node's path.normalize(path.join(cwd, out_slice)) - var path = VirtualMachine.vm.bundler.fs.absBuf(&parts, &buf); + pub fn readFileAsBytes( + this: void, + ctx: js.JSContextRef, + function: js.JSObjectRef, + thisObject: js.JSObjectRef, + arguments: []const js.JSValueRef, + exception: js.ExceptionRef, + ) js.JSValueRef { + var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; + const path = getFilePath(ctx, arguments, &buf, exception) orelse return null; buf[path.len] = 0; const buf_z: [:0]const u8 = buf[0..path.len :0]; - var file = std.fs.cwd().openFileZ(buf_z, .{ .read = true, .write = false }) catch |err| { - JSError(getAllocator(ctx), "Opening file {s} for path: \"{s}\"", .{ @errorName(err), path }, ctx, exception); - return js.JSValueMakeUndefined(ctx); - }; - - defer file.close(); - - const stat = file.stat() catch |err| { - JSError(getAllocator(ctx), "Getting file size {s} for \"{s}\"", .{ @errorName(err), path }, ctx, exception); - return js.JSValueMakeUndefined(ctx); - }; - - if (stat.kind != .File) { - JSError(getAllocator(ctx), "Can't read a {s} as a string (\"{s}\")", .{ @tagName(stat.kind), path }, ctx, exception); - return js.JSValueMakeUndefined(ctx); - } - - var contents_buf = VirtualMachine.vm.allocator.alloc(u8, stat.size + 2) catch unreachable; // OOM - defer VirtualMachine.vm.allocator.free(contents_buf); - const contents_len = file.readAll(contents_buf) catch |err| { - JSError(getAllocator(ctx), "{s} reading file (\"{s}\")", .{ @errorName(err), path }, ctx, exception); - return js.JSValueMakeUndefined(ctx); - }; + const result = readFileAsBytesCallback(ctx, buf_z, exception); + return result; + } - contents_buf[contents_len] = 0; + pub fn readFileAsString( + this: void, + ctx: js.JSContextRef, + function: js.JSObjectRef, + thisObject: js.JSObjectRef, + arguments: []const js.JSValueRef, + exception: js.ExceptionRef, + ) js.JSValueRef { + var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; + const path = getFilePath(ctx, arguments, &buf, exception) orelse return null; + buf[path.len] = 0; - // Very slow to do it this way. We're copying the string twice. - // But it's important that this string is garbage collected instead of manually managed. - // We can't really recycle this one. - // TODO: use external string - return js.JSValueMakeString(ctx, js.JSStringCreateWithUTF8CString(contents_buf.ptr)); + const buf_z: [:0]const u8 = buf[0..path.len :0]; + const result = readFileAsStringCallback(ctx, buf_z, exception); + return result; } pub fn getPublicPath(to: string, comptime Writer: type, writer: Writer) void { @@ -649,17 +705,14 @@ pub const VirtualMachine = struct { &vm.bundler.fs.fs, ) orelse 0), }; - } else if (strings.eqlComptime(_specifier, Runtime.Runtime.Imports.Name)) { + } else if (vm.node_modules == null and strings.eqlComptime(_specifier, Runtime.Runtime.Imports.Name)) { return ResolvedSource{ .allocator = null, .source_code = ZigString.init(Runtime.Runtime.sourceContent()), .specifier = ZigString.init(Runtime.Runtime.Imports.Name), .source_url = ZigString.init(Runtime.Runtime.Imports.Name), .hash = Runtime.Runtime.versionHash(), - .bytecodecache_fd = std.math.lossyCast( - u64, - Runtime.Runtime.byteCodeCacheFile(&vm.bundler.fs.fs) orelse 0, - ), + .bytecodecache_fd = 0, }; // This is all complicated because the imports have to be linked and we want to run the printer on it // so it consistently handles bundled imports @@ -723,6 +776,19 @@ pub const VirtualMachine = struct { .hash = 0, .bytecodecache_fd = 0, }; + } else if (_specifier.len > js_ast.Macro.namespaceWithColon.len and + strings.eqlComptimeIgnoreLen(_specifier[0..js_ast.Macro.namespaceWithColon.len], js_ast.Macro.namespaceWithColon)) + { + if (vm.macro_entry_points.get(MacroEntryPoint.generateIDFromSpecifier(_specifier))) |entry| { + return ResolvedSource{ + .allocator = null, + .source_code = ZigString.init(entry.source.contents), + .specifier = ZigString.init(_specifier), + .source_url = ZigString.init(_specifier), + .hash = 0, + .bytecodecache_fd = 0, + }; + } } const specifier = normalizeSpecifier(_specifier); @@ -752,6 +818,7 @@ pub const VirtualMachine = struct { vm.bundler.log = log; vm.bundler.linker.log = log; vm.bundler.resolver.log = log; + defer { vm.bundler.log = old; vm.bundler.linker.log = old; @@ -825,7 +892,7 @@ pub const VirtualMachine = struct { std.debug.assert(VirtualMachine.vm_loaded); std.debug.assert(VirtualMachine.vm.global == global); - if (vm.node_modules == null and strings.eqlComptime(specifier, Runtime.Runtime.Imports.Name)) { + if (vm.node_modules == null and strings.eqlComptime(std.fs.path.basename(specifier), Runtime.Runtime.Imports.alt_name)) { ret.path = Runtime.Runtime.Imports.Name; return; } else if (vm.node_modules != null and strings.eql(specifier, bun_file_import_path)) { diff --git a/src/js_ast.zig b/src/js_ast.zig index 9ddafdf80..0a879b8cd 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -985,7 +985,7 @@ pub const E = struct { /// lineNumber: number | null, /// }``` /// - `children`: - /// - multiple children? the function is React.jsxsDEV, "jsxs" instead of "jsx" + /// - static the function is React.jsxsDEV, "jsxs" instead of "jsx" /// - one child? the function is React.jsxDEV, /// - no children? the function is React.jsxDEV and children is an empty array. /// `isStaticChildren`: https://github.com/facebook/react/blob/4ca62cac45c288878d2532e5056981d177f9fdac/packages/react/src/jsx/ReactJSXElementValidator.js#L369-L384 @@ -1034,6 +1034,7 @@ pub const E = struct { pub const Number = struct { value: f64, + pub fn jsonStringify(self: *const Number, opts: anytype, o: anytype) !void { return try std.json.stringify(self.value, opts, o); } @@ -1042,6 +1043,8 @@ pub const E = struct { pub const BigInt = struct { value: string, + pub var empty = BigInt{ .value = "" }; + pub fn jsonStringify(self: *const @This(), opts: anytype, o: anytype) !void { return try std.json.stringify(self.value, opts, o); } @@ -1057,10 +1060,16 @@ pub const E = struct { pub const Spread = struct { value: ExprNodeIndex }; pub const String = struct { - value: JavascriptString = &([_]u16{}), + value: []const u16 = &.{}, utf8: string = &([_]u8{}), prefer_template: bool = false, + pub var empty = String{}; + pub var @"true" = String{ .utf8 = "true" }; + pub var @"false" = String{ .utf8 = "false" }; + pub var @"null" = String{ .utf8 = "null" }; + pub var @"undefined" = String{ .utf8 = "undefined" }; + pub fn clone(str: *const String, allocator: *std.mem.Allocator) !String { if (str.isUTF8()) { return String{ @@ -1100,7 +1109,7 @@ pub const E = struct { string => { return strings.eql(s.utf8, other); }, - JavascriptString => { + []u16, []const u16 => { return strings.utf16EqlString(other, s.utf8); }, else => { @@ -1119,7 +1128,7 @@ pub const E = struct { string => { return strings.utf16EqlString(s.value, other); }, - JavascriptString => { + []u16, []const u16 => { return std.mem.eql(u16, other.value, s.value); }, else => { @@ -1145,7 +1154,7 @@ pub const E = struct { return std.hash.Wyhash.hash(0, s.utf8); } else { // hash utf-16 - return std.hash.Wyhash.hash(0, @ptrCast([*]u8, s.value.ptr)[0 .. s.value.len * 2]); + return std.hash.Wyhash.hash(0, @ptrCast([*]const u8, s.value.ptr)[0 .. s.value.len * 2]); } } @@ -1180,6 +1189,46 @@ pub const E = struct { pub const RegExp = struct { value: string, + // This exists for JavaScript bindings + // The RegExp constructor expects flags as a second argument. + // We want to avoid re-lexing the flags, so we store them here. + // This is the index of the first character in a flag, not the "/" + // /foo/gim + // ^ + flags_offset: ?u16 = null, + + pub var empty = RegExp{ .value = "" }; + + pub fn pattern(this: RegExp) string { + + // rewind until we reach the /foo/gim + // ^ + // should only ever be a single character + // but we're being cautious + if (this.flags_offset) |i_| { + var i = i_; + while (i > 0 and this.value[i] != '/') { + i -= 1; + } + + return std.mem.trim(u8, this.value[0..i], "/"); + } + + return std.mem.trim(u8, this.value, "/"); + } + + pub fn flags(this: RegExp) string { + // rewind until we reach the /foo/gim + // ^ + // should only ever be a single character + // but we're being cautious + if (this.flags_offset) |i| { + return this.value[i..]; + } + + return ""; + } + pub fn jsonStringify(self: *const RegExp, opts: anytype, o: anytype) !void { return try std.json.stringify(self.value, opts, o); } @@ -2544,160 +2593,8 @@ pub const Expr = struct { e_class, e_require, - pub inline fn toPublicValue(this: Tag) u16 { - return @intCast(u16, @enumToInt(this)) + 16; - } - - pub inline fn fromPublicValue(comptime ValueType: type, value: ValueType) ?Tag { - if (value < 16 or value > @enumToInt(Tag.e_require)) return null; - - switch (comptime ValueType) { - f64 => { - return @intToEnum(@floatToInt(u16, value - 16), Tag); - }, - else => { - return @intToEnum(@intCast(u6, @intCast(u16, value) - 16), Tag); - }, - } - } - - pub const names_strings = [_]string{ - "<array>", - "<unary>", - "<binary>", - "<boolean>", - "<super>", - "<null>", - "<void>", - "<new>", - "<function>", - "<ntarget>", - "<import>", - "<call>", - "<dot>", - "<index>", - "<arrow>", - "<id>", - "<importid>", - "<private>", - "<jsx>", - "<missing>", - "<number>", - "<bigint>", - "<object>", - "<spread>", - "<string>", - "<tpart>", - "<template>", - "<regexp>", - "<await>", - "<yield>", - "<if>", - "<resolve>", - "<import>", - "<this>", - "<class>", - "<require>", - }; - pub const valid_names_list: string = brk: { - var names_list = names_strings[0]; - for (names_strings[1..]) |name_str, i| { - names_list = names_list ++ "\n " ++ name_str; - } - break :brk " " ++ names_list; - }; - - pub const TagName = std.EnumArray(Tag, string); - - pub const names: TagName = brk: { - var array = TagName.initUndefined(); - array.set(.e_array, names_strings[0]); - array.set(.e_unary, names_strings[1]); - array.set(.e_binary, names_strings[2]); - array.set(.e_boolean, names_strings[3]); - array.set(.e_super, names_strings[4]); - array.set(.e_null, names_strings[5]); - array.set(.e_undefined, names_strings[6]); - array.set(.e_new, names_strings[7]); - array.set(.e_function, names_strings[8]); - array.set(.e_new_target, names_strings[9]); - array.set(.e_import_meta, names_strings[10]); - array.set(.e_call, names_strings[11]); - array.set(.e_dot, names_strings[12]); - array.set(.e_index, names_strings[13]); - array.set(.e_arrow, names_strings[14]); - array.set(.e_identifier, names_strings[15]); - array.set(.e_import_identifier, names_strings[16]); - array.set(.e_private_identifier, names_strings[17]); - array.set(.e_jsx_element, names_strings[18]); - array.set(.e_missing, names_strings[19]); - array.set(.e_number, names_strings[20]); - array.set(.e_big_int, names_strings[21]); - array.set(.e_object, names_strings[22]); - array.set(.e_spread, names_strings[23]); - array.set(.e_string, names_strings[24]); - array.set(.e_template_part, names_strings[25]); - array.set(.e_template, names_strings[26]); - array.set(.e_reg_exp, names_strings[27]); - array.set(.e_await, names_strings[28]); - array.set(.e_yield, names_strings[29]); - array.set(.e_if, names_strings[30]); - array.set(.e_require_or_require_resolve, names_strings[31]); - array.set(.e_import, names_strings[32]); - array.set(.e_this, names_strings[33]); - array.set(.e_class, names_strings[34]); - array.set(.e_require, names_strings[35]); - break :brk array; - }; - pub const TagExactSizeMatcher = strings.ExactSizeMatcher(8); - pub fn find(name_: string) ?Tag { - return switch (TagExactSizeMatcher.match(name_)) { - TagExactSizeMatcher.case("array") => Tag.e_array, - TagExactSizeMatcher.case("unary") => Tag.e_unary, - TagExactSizeMatcher.case("binary") => Tag.e_binary, - TagExactSizeMatcher.case("boolean") => Tag.e_boolean, - TagExactSizeMatcher.case("true") => Tag.e_boolean, - TagExactSizeMatcher.case("false") => Tag.e_boolean, - TagExactSizeMatcher.case("super") => Tag.e_super, - TagExactSizeMatcher.case("null") => Tag.e_null, - TagExactSizeMatcher.case("void") => Tag.e_undefined, - TagExactSizeMatcher.case("new") => Tag.e_new, - TagExactSizeMatcher.case("function") => Tag.e_function, - TagExactSizeMatcher.case("ntarget") => Tag.e_new_target, - TagExactSizeMatcher.case("imeta") => Tag.e_import_meta, - TagExactSizeMatcher.case("call") => Tag.e_call, - TagExactSizeMatcher.case("dot") => Tag.e_dot, - TagExactSizeMatcher.case("index") => Tag.e_index, - TagExactSizeMatcher.case("arrow") => Tag.e_arrow, - TagExactSizeMatcher.case("id") => Tag.e_identifier, - TagExactSizeMatcher.case("importid") => Tag.e_import_identifier, - TagExactSizeMatcher.case("jsx") => Tag.e_jsx_element, - TagExactSizeMatcher.case("missing") => Tag.e_missing, - TagExactSizeMatcher.case("number") => Tag.e_number, - TagExactSizeMatcher.case("bigint") => Tag.e_big_int, - TagExactSizeMatcher.case("object") => Tag.e_object, - TagExactSizeMatcher.case("spread") => Tag.e_spread, - TagExactSizeMatcher.case("string") => Tag.e_string, - TagExactSizeMatcher.case("tpart") => Tag.e_template_part, - TagExactSizeMatcher.case("template") => Tag.e_template, - TagExactSizeMatcher.case("regexp") => Tag.e_reg_exp, - TagExactSizeMatcher.case("await") => Tag.e_await, - TagExactSizeMatcher.case("yield") => Tag.e_yield, - TagExactSizeMatcher.case("if") => Tag.e_if, - TagExactSizeMatcher.case("import") => Tag.e_import, - TagExactSizeMatcher.case("this") => Tag.e_this, - TagExactSizeMatcher.case("class") => Tag.e_class, - TagExactSizeMatcher.case("require") => Tag.e_require, - else => null, - }; - } - - pub inline fn name(this: Tag) string { - return names.get(this); - } - pub fn jsonStringify(self: @This(), opts: anytype, o: anytype) !void { - return try std.json.stringify(self.name(), opts, o); + return try std.json.stringify(@tagName(self), opts, o); } pub fn isArray(self: Tag) bool { @@ -3352,7 +3249,7 @@ pub const S = struct { pub const Comment = struct { text: string }; pub const Directive = struct { - value: JavascriptString, + value: []const u16, }; pub const ExportClause = struct { items: []ClauseItem, is_single_line: bool = false }; @@ -4204,38 +4101,2012 @@ pub const Macro = struct { } }; - pub const JSExpr = struct { - expr: Expr, + pub const MacroResult = struct { + import_statements: []S.Import = &[_]S.Import{}, + replacement: Expr, + }; + + pub const JSNode = struct { + loc: logger.Loc, + data: Data, pub const Class = JSCBase.NewClass( - JSExpr, + JSNode, .{ - .name = "JSExpr", + .name = "JSNode", .read_only = true, }, .{ .toString = .{ - .rfn = toString, + .rfn = JSBindings.toString, }, + + // .getAt = .{ + // .rfn = JSBindings.getAt, + // }, + // .valueAt = .{ + // .rfn = JSBindings.valueAt, + // }, // .toNumber = .{ // .rfn = toNumber, // }, + .get = .{ + .rfn = JSBindings.get, + .ro = true, + }, }, .{ .tag = .{ - .get = getTag, + .get = JSBindings.getTag, .ro = true, }, + .tagName = .{ - .get = getTagName, + .get = JSBindings.getTagName, .ro = true, }, .position = .{ - .get = getPosition, + .get = JSBindings.getPosition, + .ro = true, + }, + .value = .{ + .get = JSBindings.getValue, + .ro = true, + }, + .arguments = .{ + .get = JSBindings.getCallArgs, + .ro = true, + }, + .properties = .{ + .get = JSBindings.getProperties, .ro = true, }, }, ); + pub fn makeFromExpr(allocator: *std.mem.Allocator, expr: Expr) js.JSObjectRef { + var ptr = allocator.create(JSNode) catch unreachable; + ptr.* = JSNode.initExpr(expr); + // If we look at JSObjectMake, we can see that all it does with the ctx value is lookup what the global object is + // so it's safe to just avoid that and do it here like this: + return JSNode.Class.make(JavaScript.VirtualMachine.vm.global.ref(), ptr); + } + pub const JSBindings = struct { + const getAllocator = JSCBase.getAllocator; + + threadlocal var temporary_call_args_array: [256]js.JSValueRef = undefined; + pub fn getCallArgs( + this: *JSNode, + ctx: js.JSContextRef, + thisObject: js.JSValueRef, + prop: js.JSStringRef, + exception: js.ExceptionRef, + ) js.JSObjectRef { + const args = this.data.callArgs(); + + switch (args.len) { + 0 => return js.JSObjectMakeArray(ctx, 0, null, exception), + 1...255 => { + var slice = temporary_call_args_array[0..args.len]; + for (slice) |_, i| { + var node = JSCBase.getAllocator(ctx).create(JSNode) catch unreachable; + node.* = JSNode.initExpr(args[i]); + slice[i] = JSNode.Class.make(ctx, node); + } + return js.JSObjectMakeArray(ctx, args.len, slice.ptr, exception); + }, + else => { + Output.prettyErrorln("are you for real? {d} args to your call expression? that has to be a bug.\n", .{args.len}); + Output.flush(); + return js.JSObjectMakeArray(ctx, 0, null, exception); + }, + } + } + + pub fn getProperties( + this: *JSNode, + ctx: js.JSContextRef, + thisObject: js.JSValueRef, + prop: js.JSStringRef, + exception: js.ExceptionRef, + ) js.JSObjectRef { + const args = if (this.data == .e_object) this.data.e_object.properties else &[_]G.Property{}; + + switch (args.len) { + 0 => return js.JSObjectMakeArray(ctx, 0, null, exception), + 1...255 => { + var slice = temporary_call_args_array[0..args.len]; + for (slice) |_, i| { + var node = JSCBase.getAllocator(ctx).create(JSNode) catch unreachable; + node.* = JSNode{ .data = .{ .g_property = &args[i] }, .loc = this.loc }; + slice[i] = JSNode.Class.make(ctx, node); + } + return js.JSObjectMakeArray(ctx, args.len, slice.ptr, exception); + }, + else => { + return js.JSObjectMakeArray(ctx, 0, null, exception); + }, + } + } + + fn toNumberValue(this: *JSNode, number: E.Number) js.JSValueRef { + return JSC.JSValue.jsNumberFromDouble(number.value).asRef(); + } + + fn toStringValue(this: *JSNode, ctx: js.JSContextRef, str: E.String) js.JSObjectRef { + if (str.isBlank()) { + return JSC.ZigString.init("").toValue(JavaScript.VirtualMachine.vm.global).asRef(); + } + + if (str.isUTF8()) { + return JSC.ZigString.init(str.utf8).toValue(JavaScript.VirtualMachine.vm.global).asRef(); + } else { + return js.JSValueMakeString(ctx, js.JSStringCreateWithCharactersNoCopy(str.value.ptr, str.value.len)); + } + } + + threadlocal var regex_value_array: [2]js.JSValueRef = undefined; + + fn toRegexValue(this: *JSNode, ctx: js.JSContextRef, regex: *E.RegExp, exception: js.ExceptionRef) js.JSObjectRef { + if (regex.value.len == 0) { + return js.JSObjectMakeRegExp(ctx, 0, null, exception); + } + + regex_value_array[0] = JSC.ZigString.init(regex.pattern()).toValue(JavaScript.VirtualMachine.vm.global).asRef(); + regex_value_array[1] = JSC.ZigString.init(regex.flags()).toValue(JavaScript.VirtualMachine.vm.global).asRef(); + + return js.JSObjectMakeRegExp(ctx, 2, ®ex_value_array, exception); + } + + fn toArrayValue(this: *JSNode, ctx: js.JSContextRef, array: E.Array, exception: js.ExceptionRef) js.JSObjectRef { + if (array.items.len == 0) { + return js.JSObjectMakeArray(ctx, 0, null, exception); + } + + for (array.items) |expr, i| { + var node = JSCBase.getAllocator(ctx).create(JSNode) catch unreachable; + node.* = JSNode.initExpr(expr); + temporary_call_args_array[i] = JSNode.Class.make(ctx, node); + } + + return js.JSObjectMakeArray(ctx, array.items.len, &temporary_call_args_array, exception); + } + + fn toArrayPrimitive(this: *JSNode, ctx: js.JSContextRef, array: E.Array, exception: js.ExceptionRef) js.JSObjectRef { + if (array.items.len == 0) { + return js.JSObjectMakeArray(ctx, 0, null, exception); + } + + var node: JSNode = undefined; + for (array.items) |expr, i| { + node = JSNode.initExpr(expr); + temporary_call_args_array[i] = toPrimitive(&node, ctx, exception); + } + + return js.JSObjectMakeArray(ctx, array.items.len, temporary_call_args_array[0..array.items.len].ptr, exception); + } + + fn toObjectValue(this: *JSNode, ctx: js.JSContextRef, obj: E.Object, exception: js.ExceptionRef) js.JSObjectRef { + if (obj.properties.len == 0) { + return js.JSObjectMakeArray(ctx, 0, null, exception); + } + + var object_properties_array: [64]js.JSObjectRef = undefined; + + var did_allocate = false; + var properties_list = if (obj.properties.len < object_properties_array.len) + object_properties_array[0..obj.properties.len] + else brk: { + did_allocate = true; + break :brk getAllocator(ctx).alloc(js.JSObjectRef, obj.properties.len) catch unreachable; + }; + + defer if (did_allocate) getAllocator(ctx).free(properties_list); + + for (obj.properties) |_, i| { + var node = JSCBase.getAllocator(ctx).create(JSNode) catch unreachable; + node.* = JSNode{ + .data = .{ + .g_property = &obj.properties[i], + }, + .loc = this.loc, + }; + properties_list[i] = JSNode.Class.make(ctx, node); + } + + return js.JSObjectMakeArray(ctx, properties_list.len, properties_list.ptr, exception); + } + + fn toObjectPrimitive(this: *JSNode, ctx: js.JSContextRef, obj: E.Object, exception: js.ExceptionRef) js.JSObjectRef { + return toObjectValue(this, ctx, obj, exception); + } + + fn toPropertyPrimitive(this: *JSNode, ctx: js.JSContextRef, prop: G.Property, exception: js.ExceptionRef) js.JSObjectRef { + var entries: [3]js.JSValueRef = undefined; + + entries[0] = js.JSValueMakeUndefined(ctx); + entries[1] = entries[0]; + entries[2] = entries[0]; + + var other: JSNode = undefined; + + if (prop.key) |key| { + other = JSNode.initExpr(key); + entries[0] = toPrimitive( + &other, + ctx, + exception, + ) orelse js.JSValueMakeUndefined(ctx); + } + + if (prop.value) |value| { + other = JSNode.initExpr(value); + entries[1] = toPrimitive( + &other, + ctx, + exception, + ) orelse js.JSValueMakeUndefined(ctx); + } + + if (prop.initializer) |value| { + other = JSNode.initExpr(value); + entries[2] = toPrimitive( + &other, + ctx, + exception, + ) orelse js.JSValueMakeUndefined(ctx); + } + + const out = js.JSObjectMakeArray(ctx, 3, &entries, exception); + return out; + } + + pub fn toString( + this: *JSNode, + ctx: js.JSContextRef, + function: js.JSObjectRef, + thisObject: js.JSObjectRef, + arguments: []const js.JSValueRef, + exception: js.ExceptionRef, + ) js.JSObjectRef { + switch (this.data) { + .e_string => |str| { + return toStringValue(this, ctx, str.*); + }, + .e_template => |template| { + const str = template.head; + + if (str.isBlank()) { + return JSC.ZigString.init("").toValue(JavaScript.VirtualMachine.vm.global).asRef(); + } + + if (str.isUTF8()) { + return JSC.ZigString.init(str.utf8).toValue(JavaScript.VirtualMachine.vm.global).asRef(); + } else { + return js.JSValueMakeString(ctx, js.JSStringCreateWithCharactersNoCopy(str.value.ptr, str.value.len)); + } + }, + // .e_number => |number| { + + // }, + else => { + return JSC.ZigString.init("").toValue(JavaScript.VirtualMachine.vm.global).asRef(); + }, + } + } + + fn toPrimitive( + this: *JSNode, + ctx: js.JSContextRef, + exception: js.ExceptionRef, + ) js.JSValueRef { + return @call(.{ .modifier = .always_inline }, toPrimitiveAllowRecursion, .{ this, ctx, exception, false }); + } + + fn toPrimitiveWithRecursion( + this: *JSNode, + ctx: js.JSContextRef, + exception: js.ExceptionRef, + ) js.JSValueRef { + return @call(.{ .modifier = .always_inline }, toPrimitiveAllowRecursion, .{ this, ctx, exception, true }); + } + + fn toPrimitiveAllowRecursion(this: *JSNode, ctx: js.JSContextRef, exception: js.ExceptionRef, comptime allow_recursion: bool) js.JSValueRef { + switch (this.data) { + .e_string => |str| { + return JSBindings.toStringValue(this, ctx, str.*); + }, + .e_template => |template| { + return JSBindings.toStringValue(this, ctx, template.head); + // return JSBindings.toTemplatePrimitive(this, ctx, template.*); + }, + .e_number => |number| { + return JSBindings.toNumberValue(this, number); + }, + .e_reg_exp => |regex| { + return JSBindings.toRegexValue(this, ctx, regex, exception); + }, + .e_object => |object| { + if (comptime !allow_recursion) return js.JSValueMakeUndefined(ctx); + return JSBindings.toObjectPrimitive(this, ctx, object.*, exception); + }, + .e_array => |array| { + if (comptime !allow_recursion) return js.JSValueMakeUndefined(ctx); + return JSBindings.toArrayPrimitive(this, ctx, array.*, exception); + }, + + // Returns an Entry + // [string, number | regex | object | string | null | undefined] + .g_property => |property| { + return JSBindings.toPropertyPrimitive(this, ctx, property.*, exception); + }, + .e_null => { + return js.JSValueMakeNull(ctx); + }, + else => { + return js.JSValueMakeUndefined(ctx); + }, + } + } + + fn toValue(this: *JSNode, ctx: js.JSContextRef, exception: js.ExceptionRef) js.JSObjectRef { + switch (this.data) { + .e_await => |aw| { + return JSNode.makeFromExpr(getAllocator(ctx), aw.value); + }, + .e_yield => |yi| { + return JSNode.makeFromExpr(getAllocator(ctx), yi.value orelse return null); + }, + .e_spread => |spread| { + return JSNode.makeFromExpr(getAllocator(ctx), spread.value); + }, + .e_reg_exp => |reg| { + return JSC.ZigString.toRef(reg.value, JavaScript.VirtualMachine.vm.global); + }, + + .e_array => |array| { + return toArrayValue(this, ctx, array.*, exception); + }, + .e_object => |obj| { + return toObjectValue(this, ctx, obj.*, exception); + }, + else => { + return null; + }, + } + } + + pub fn getValue( + this: *JSNode, + ctx: js.JSContextRef, + thisObject: js.JSValueRef, + prop: js.JSStringRef, + exception: js.ExceptionRef, + ) js.JSObjectRef { + return toValue(this, ctx, exception) orelse return thisObject; + } + + pub fn get( + this: *JSNode, + ctx: js.JSContextRef, + function: js.JSObjectRef, + thisObject: js.JSObjectRef, + arguments: []const js.JSValueRef, + exception: js.ExceptionRef, + ) js.JSObjectRef { + return toPrimitiveWithRecursion(this, ctx, exception) orelse return js.JSValueMakeUndefined(ctx); + } + + pub fn getTag( + this: *JSNode, + ctx: js.JSContextRef, + thisObject: js.JSValueRef, + prop: js.JSStringRef, + exception: js.ExceptionRef, + ) js.JSObjectRef { + return JSC.JSValue.jsNumberFromU16(@intCast(u16, @enumToInt(std.meta.activeTag(this.data)))).asRef(); + } + pub fn getTagName( + this: *JSNode, + ctx: js.JSContextRef, + thisObject: js.JSValueRef, + prop: js.JSStringRef, + exception: js.ExceptionRef, + ) js.JSObjectRef { + return JSC.ZigString.init(@tagName(this.data)).toValue(JavaScript.VirtualMachine.vm.global).asRef(); + } + pub fn getPosition( + this: *JSNode, + ctx: js.JSContextRef, + thisObject: js.JSValueRef, + prop: js.JSStringRef, + exception: js.ExceptionRef, + ) js.JSObjectRef { + return JSC.JSValue.jsNumberFromInt32(this.loc.start).asRef(); + } + }; + + pub fn initExpr(this: Expr) JSNode { + switch (this.data) { + .e_array => |value| { + return JSNode{ .loc = this.loc, .data = .{ .e_array = value } }; + }, + .e_unary => |value| { + return JSNode{ .loc = this.loc, .data = .{ .e_unary = value } }; + }, + .e_binary => |value| { + return JSNode{ .loc = this.loc, .data = .{ .e_binary = value } }; + }, + .e_function => |value| { + return JSNode{ .loc = this.loc, .data = .{ .e_function = value } }; + }, + .e_new_target => |value| { + return JSNode{ .loc = this.loc, .data = .{ .e_new_target = value } }; + }, + .e_import_meta => |value| { + return JSNode{ .loc = this.loc, .data = .{ .e_import_meta = value } }; + }, + .e_call => |value| { + return JSNode{ .loc = this.loc, .data = .{ .e_call = value } }; + }, + .e_dot => |value| { + return JSNode{ .loc = this.loc, .data = .{ .e_dot = value } }; + }, + .e_index => |value| { + return JSNode{ .loc = this.loc, .data = .{ .e_index = value } }; + }, + .e_arrow => |value| { + return JSNode{ .loc = this.loc, .data = .{ .e_arrow = value } }; + }, + .e_identifier => |value| { + return JSNode{ .loc = this.loc, .data = .{ .e_identifier = value } }; + }, + .e_import_identifier => |value| { + return JSNode{ .loc = this.loc, .data = .{ .e_import_identifier = value } }; + }, + .e_private_identifier => |value| { + return JSNode{ .loc = this.loc, .data = .{ .e_private_identifier = value } }; + }, + .e_jsx_element => |value| { + return JSNode{ .loc = this.loc, .data = .{ .e_jsx_element = value } }; + }, + .e_big_int => |value| { + return JSNode{ .loc = this.loc, .data = .{ .e_big_int = value } }; + }, + .e_object => |value| { + return JSNode{ .loc = this.loc, .data = .{ .e_object = value } }; + }, + .e_spread => |value| { + return JSNode{ .loc = this.loc, .data = .{ .e_spread = value } }; + }, + .e_string => |value| { + return JSNode{ .loc = this.loc, .data = .{ .e_string = value } }; + }, + .e_template_part => |value| { + return JSNode{ .loc = this.loc, .data = .{ .e_template_part = value } }; + }, + .e_template => |value| { + return JSNode{ .loc = this.loc, .data = .{ .e_template = value } }; + }, + .e_reg_exp => |value| { + return JSNode{ .loc = this.loc, .data = .{ .e_reg_exp = value } }; + }, + .e_await => |value| { + return JSNode{ .loc = this.loc, .data = .{ .e_await = value } }; + }, + .e_yield => |value| { + return JSNode{ .loc = this.loc, .data = .{ .e_yield = value } }; + }, + .e_if => |value| { + return JSNode{ .loc = this.loc, .data = .{ .e_if = value } }; + }, + .e_require_or_require_resolve => |value| { + return JSNode{ .loc = this.loc, .data = .{ .e_require_or_require_resolve = value } }; + }, + .e_import => |value| { + return JSNode{ .loc = this.loc, .data = .{ .e_import = value } }; + }, + .e_this => |value| { + return JSNode{ .loc = this.loc, .data = .{ .e_this = value } }; + }, + .e_class => |value| { + return JSNode{ .loc = this.loc, .data = .{ .e_class = value } }; + }, + .e_require => |value| { + return JSNode{ .loc = this.loc, .data = .{ .e_require = value } }; + }, + .e_missing => |value| { + return JSNode{ .loc = this.loc, .data = .{ .e_missing = value } }; + }, + .e_boolean => |value| { + return JSNode{ .loc = this.loc, .data = .{ .e_boolean = value } }; + }, + .e_super => |value| { + return JSNode{ .loc = this.loc, .data = .{ .e_super = value } }; + }, + .e_null => |value| { + return JSNode{ .loc = this.loc, .data = .{ .e_null = value } }; + }, + .e_number => |value| { + return JSNode{ .loc = this.loc, .data = .{ .e_number = value } }; + }, + .e_undefined => |value| { + return JSNode{ .loc = this.loc, .data = .{ .e_undefined = value } }; + }, + else => { + return JSNode{ .loc = this.loc, .data = .{ .e_missing = .{} } }; + }, + } + } + + pub fn toExpr(this: JSNode) Expr { + switch (this.data) { + .e_array => |value| { + return Expr{ .loc = this.loc, .data = .{ .e_array = value } }; + }, + .e_unary => |value| { + return Expr{ .loc = this.loc, .data = .{ .e_unary = value } }; + }, + .e_binary => |value| { + return Expr{ .loc = this.loc, .data = .{ .e_binary = value } }; + }, + .e_function => |value| { + return Expr{ .loc = this.loc, .data = .{ .e_function = value } }; + }, + .e_new_target => |value| { + return Expr{ .loc = this.loc, .data = .{ .e_new_target = value } }; + }, + .e_import_meta => |value| { + return Expr{ .loc = this.loc, .data = .{ .e_import_meta = value } }; + }, + .e_call => |value| { + return Expr{ .loc = this.loc, .data = .{ .e_call = value } }; + }, + .e_dot => |value| { + return Expr{ .loc = this.loc, .data = .{ .e_dot = value } }; + }, + .e_index => |value| { + return Expr{ .loc = this.loc, .data = .{ .e_index = value } }; + }, + .e_arrow => |value| { + return Expr{ .loc = this.loc, .data = .{ .e_arrow = value } }; + }, + .e_identifier => |value| { + return Expr{ .loc = this.loc, .data = .{ .e_identifier = value } }; + }, + .e_import_identifier => |value| { + return Expr{ .loc = this.loc, .data = .{ .e_import_identifier = value } }; + }, + .e_private_identifier => |value| { + return Expr{ .loc = this.loc, .data = .{ .e_private_identifier = value } }; + }, + .e_jsx_element => |value| { + return Expr{ .loc = this.loc, .data = .{ .e_jsx_element = value } }; + }, + .e_big_int => |value| { + return Expr{ .loc = this.loc, .data = .{ .e_big_int = value } }; + }, + .e_object => |value| { + return Expr{ .loc = this.loc, .data = .{ .e_object = value } }; + }, + .e_spread => |value| { + return Expr{ .loc = this.loc, .data = .{ .e_spread = value } }; + }, + .e_string => |value| { + return Expr{ .loc = this.loc, .data = .{ .e_string = value } }; + }, + .e_template_part => |value| { + return Expr{ .loc = this.loc, .data = .{ .e_template_part = value } }; + }, + .e_template => |value| { + return Expr{ .loc = this.loc, .data = .{ .e_template = value } }; + }, + .e_reg_exp => |value| { + return Expr{ .loc = this.loc, .data = .{ .e_reg_exp = value } }; + }, + .e_await => |value| { + return Expr{ .loc = this.loc, .data = .{ .e_await = value } }; + }, + .e_yield => |value| { + return Expr{ .loc = this.loc, .data = .{ .e_yield = value } }; + }, + .e_if => |value| { + return Expr{ .loc = this.loc, .data = .{ .e_if = value } }; + }, + .e_require_or_require_resolve => |value| { + return Expr{ .loc = this.loc, .data = .{ .e_require_or_require_resolve = value } }; + }, + .e_import => |value| { + return Expr{ .loc = this.loc, .data = .{ .e_import = value } }; + }, + .e_this => |value| { + return Expr{ .loc = this.loc, .data = .{ .e_this = value } }; + }, + .e_class => |value| { + return Expr{ .loc = this.loc, .data = .{ .e_class = value } }; + }, + .e_require => |value| { + return Expr{ .loc = this.loc, .data = .{ .e_require = value } }; + }, + .e_missing => |value| { + return Expr{ .loc = this.loc, .data = .{ .e_missing = value } }; + }, + .e_boolean => |value| { + return Expr{ .loc = this.loc, .data = .{ .e_boolean = value } }; + }, + .e_super => |value| { + return Expr{ .loc = this.loc, .data = .{ .e_super = value } }; + }, + .e_null => |value| { + return Expr{ .loc = this.loc, .data = .{ .e_null = value } }; + }, + .e_number => |value| { + return Expr{ .loc = this.loc, .data = .{ .e_number = value } }; + }, + .e_undefined => |value| { + return Expr{ .loc = this.loc, .data = .{ .e_undefined = value } }; + }, + else => { + return Expr{ .loc = this.loc, .data = .{ .e_missing = .{} } }; + }, + } + } + + pub const Data = union(Tag) { + inline_false: void, + inline_true: void, + e_boolean: E.Boolean, + e_super: E.Super, + e_null: E.Null, + e_number: E.Number, + e_undefined: E.Undefined, + e_new_target: E.NewTarget, + e_import_meta: E.ImportMeta, + e_missing: E.Missing, + e_this: E.This, + + e_array: *E.Array, + e_unary: *E.Unary, + e_binary: *E.Binary, + e_function: *E.Function, + + e_call: *E.Call, + e_dot: *E.Dot, + e_index: *E.Index, + e_arrow: *E.Arrow, + e_identifier: *E.Identifier, + e_import_identifier: *E.ImportIdentifier, + e_private_identifier: *E.PrivateIdentifier, + e_jsx_element: *E.JSXElement, + + e_big_int: *E.BigInt, + e_object: *E.Object, + e_spread: *E.Spread, + e_string: *E.String, + e_template_part: *E.TemplatePart, + e_template: *E.Template, + e_reg_exp: *E.RegExp, + e_await: *E.Await, + e_yield: *E.Yield, + e_if: *E.If, + e_require_or_require_resolve: *E.RequireOrRequireResolve, + e_import: *E.Import, + + e_class: *E.Class, + e_require: *E.Require, + + s_import: *S.Import, + s_block: *S.Block, + + g_property: *G.Property, + + pub fn callArgs(this: Data) ExprNodeList { + if (this == .e_call) + return this.e_call.args + else + return &[_]Expr{}; + } + + pub fn booleanValue(this: Data) bool { + return switch (this) { + .inline_false => false, + .inline_true => true, + .e_boolean => this.e_boolean.value, + }; + } + }; + pub const Tag = enum(u8) { + e_array, + e_unary, + e_binary, + e_function, + e_new_target, + e_import_meta, + e_call, + e_dot, + e_index, + e_arrow, + e_identifier, + e_import_identifier, + e_private_identifier, + e_jsx_element, + e_big_int, + e_object, + e_spread, + e_string, + e_template_part, + e_template, + e_reg_exp, + e_await, + e_yield, + e_if, + e_require_or_require_resolve, + e_import, + e_this, + e_class, + e_require, + s_import, + s_block, + + g_property, + + e_missing, + e_boolean, + e_super, + e_null, + e_number, + e_undefined, + + inline_true, + inline_false, + + pub const ids: std.EnumArray(Tag, Expr.Data) = brk: { + var list = std.EnumArray(Tag, Expr.Data).initFill(Expr.Data{ .e_number = E.Number{ .value = 0.0 } }); + list.set(Tag.e_array, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_array)) }, + }); + list.set(Tag.e_unary, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_unary)) }, + }); + list.set(Tag.e_binary, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_binary)) }, + }); + list.set(Tag.e_boolean, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_boolean)) }, + }); + list.set(Tag.e_super, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_super)) }, + }); + list.set(Tag.e_null, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_null)) }, + }); + list.set(Tag.e_undefined, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_undefined)) }, + }); + list.set(Tag.e_function, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_function)) }, + }); + list.set(Tag.e_new_target, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_new_target)) }, + }); + list.set(Tag.e_import_meta, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_import_meta)) }, + }); + list.set(Tag.e_call, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_call)) }, + }); + list.set(Tag.e_dot, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_dot)) }, + }); + list.set(Tag.e_index, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_index)) }, + }); + list.set(Tag.e_arrow, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_arrow)) }, + }); + list.set(Tag.e_identifier, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_identifier)) }, + }); + list.set(Tag.e_import_identifier, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_import_identifier)) }, + }); + list.set(Tag.e_private_identifier, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_private_identifier)) }, + }); + list.set(Tag.e_jsx_element, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_jsx_element)) }, + }); + list.set(Tag.e_missing, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_missing)) }, + }); + list.set(Tag.e_number, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_number)) }, + }); + list.set(Tag.e_big_int, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_big_int)) }, + }); + list.set(Tag.e_object, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_object)) }, + }); + list.set(Tag.e_spread, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_spread)) }, + }); + list.set(Tag.e_string, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_string)) }, + }); + list.set(Tag.e_template_part, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_template_part)) }, + }); + list.set(Tag.e_template, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_template)) }, + }); + list.set(Tag.e_reg_exp, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_reg_exp)) }, + }); + list.set(Tag.e_await, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_await)) }, + }); + list.set(Tag.e_yield, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_yield)) }, + }); + list.set(Tag.e_if, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_if)) }, + }); + list.set(Tag.e_import, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_import)) }, + }); + list.set(Tag.e_this, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_this)) }, + }); + list.set(Tag.e_class, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_class)) }, + }); + list.set(Tag.e_require, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.e_require)) }, + }); + list.set(Tag.s_import, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.s_import)) }, + }); + list.set(Tag.g_property, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.g_property)) }, + }); + list.set(Tag.s_block, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.s_block)) }, + }); + list.set(Tag.inline_true, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.inline_true)) }, + }); + list.set(Tag.inline_false, Expr.Data{ + .e_number = E.Number{ .value = @intToFloat(f64, @enumToInt(Tag.inline_false)) }, + }); + break :brk list; + }; + + pub const names = std.ComptimeStringMap(Tag, .{ + .{ "array", Tag.e_array }, + .{ "unary", Tag.e_unary }, + .{ "binary", Tag.e_binary }, + .{ "bool", Tag.e_boolean }, + .{ "super", Tag.e_super }, + .{ "null", Tag.e_null }, + .{ "undefined", Tag.e_undefined }, + .{ "function", Tag.e_function }, + .{ "new_target", Tag.e_new_target }, + .{ "import_meta", Tag.e_import_meta }, + .{ "call", Tag.e_call }, + .{ "dot", Tag.e_dot }, + .{ "index", Tag.e_index }, + .{ "arrow", Tag.e_arrow }, + .{ "id", Tag.e_identifier }, + .{ "import-id", Tag.e_import_identifier }, + .{ "private-id", Tag.e_private_identifier }, + .{ "jsx", Tag.e_jsx_element }, + .{ "missing", Tag.e_missing }, + .{ "number", Tag.e_number }, + .{ "bigint", Tag.e_big_int }, + .{ "object", Tag.e_object }, + .{ "spread", Tag.e_spread }, + .{ "string", Tag.e_string }, + .{ "template-part", Tag.e_template_part }, + .{ "template", Tag.e_template }, + .{ "regex", Tag.e_reg_exp }, + .{ "await", Tag.e_await }, + .{ "yield", Tag.e_yield }, + .{ "if", Tag.e_if }, + .{ "dynamic", Tag.e_import }, + .{ "this", Tag.e_this }, + .{ "class", Tag.e_class }, + .{ "require", Tag.e_require }, + .{ "import", Tag.s_import }, + .{ "property", Tag.g_property }, + .{ "block", Tag.s_block }, + .{ "true", Tag.inline_true }, + .{ "false", Tag.inline_false }, + }); + + pub const as_expr_tag: std.EnumArray(Tag, Expr.Tag) = brk: { + var list = std.EnumArray(Tag, Expr.Tag).initFill(Expr.Tag.e_missing); + list.set(Tag.e_array, Expr.Tag.e_array); + list.set(Tag.e_unary, Expr.Tag.e_unary); + list.set(Tag.e_binary, Expr.Tag.e_binary); + list.set(Tag.e_boolean, Expr.Tag.e_boolean); + list.set(Tag.e_super, Expr.Tag.e_super); + list.set(Tag.e_null, Expr.Tag.e_null); + list.set(Tag.e_undefined, Expr.Tag.e_undefined); + list.set(Tag.e_function, Expr.Tag.e_function); + list.set(Tag.e_new_target, Expr.Tag.e_new_target); + list.set(Tag.e_import_meta, Expr.Tag.e_import_meta); + list.set(Tag.e_call, Expr.Tag.e_call); + list.set(Tag.e_dot, Expr.Tag.e_dot); + list.set(Tag.e_index, Expr.Tag.e_index); + list.set(Tag.e_arrow, Expr.Tag.e_arrow); + list.set(Tag.e_identifier, Expr.Tag.e_identifier); + list.set(Tag.e_import_identifier, Expr.Tag.e_import_identifier); + list.set(Tag.e_private_identifier, Expr.Tag.e_private_identifier); + list.set(Tag.e_jsx_element, Expr.Tag.e_jsx_element); + list.set(Tag.e_missing, Expr.Tag.e_missing); + list.set(Tag.e_number, Expr.Tag.e_number); + list.set(Tag.e_big_int, Expr.Tag.e_big_int); + list.set(Tag.e_object, Expr.Tag.e_object); + list.set(Tag.e_spread, Expr.Tag.e_spread); + list.set(Tag.e_string, Expr.Tag.e_string); + list.set(Tag.e_template_part, Expr.Tag.e_template_part); + list.set(Tag.e_template, Expr.Tag.e_template); + list.set(Tag.e_reg_exp, Expr.Tag.e_reg_exp); + list.set(Tag.e_await, Expr.Tag.e_await); + list.set(Tag.e_yield, Expr.Tag.e_yield); + list.set(Tag.e_if, Expr.Tag.e_if); + list.set(Tag.e_require_or_require_resolve, Expr.Tag.e_require_or_require_resolve); + list.set(Tag.e_import, Expr.Tag.e_import); + list.set(Tag.e_this, Expr.Tag.e_this); + list.set(Tag.e_class, Expr.Tag.e_class); + list.set(Tag.e_require, Expr.Tag.e_require); + break :brk list; + }; + + pub const to_expr_tag: std.EnumArray(Expr.Tag, Tag) = brk: { + var list = std.EnumArray(Expr.Tag, Tag).initFill(Tag.wip); + list.set(Expr.Tag.e_array, Tag.e_array); + list.set(Expr.Tag.e_unary, Tag.e_unary); + list.set(Expr.Tag.e_binary, Tag.e_binary); + list.set(Expr.Tag.e_boolean, Tag.e_boolean); + list.set(Expr.Tag.e_super, Tag.e_super); + list.set(Expr.Tag.e_null, Tag.e_null); + list.set(Expr.Tag.e_undefined, Tag.e_undefined); + list.set(Expr.Tag.e_function, Tag.e_function); + list.set(Expr.Tag.e_new_target, Tag.e_new_target); + list.set(Expr.Tag.e_import_meta, Tag.e_import_meta); + list.set(Expr.Tag.e_call, Tag.e_call); + list.set(Expr.Tag.e_dot, Tag.e_dot); + list.set(Expr.Tag.e_index, Tag.e_index); + list.set(Expr.Tag.e_arrow, Tag.e_arrow); + list.set(Expr.Tag.e_identifier, Tag.e_identifier); + list.set(Expr.Tag.e_import_identifier, Tag.e_import_identifier); + list.set(Expr.Tag.e_private_identifier, Tag.e_private_identifier); + list.set(Expr.Tag.e_jsx_element, Tag.e_jsx_element); + list.set(Expr.Tag.e_missing, Tag.e_missing); + list.set(Expr.Tag.e_number, Tag.e_number); + list.set(Expr.Tag.e_big_int, Tag.e_big_int); + list.set(Expr.Tag.e_object, Tag.e_object); + list.set(Expr.Tag.e_spread, Tag.e_spread); + list.set(Expr.Tag.e_string, Tag.e_string); + list.set(Expr.Tag.e_template_part, Tag.e_template_part); + list.set(Expr.Tag.e_template, Tag.e_template); + list.set(Expr.Tag.e_reg_exp, Tag.e_reg_exp); + list.set(Expr.Tag.e_await, Tag.e_await); + list.set(Expr.Tag.e_yield, Tag.e_yield); + list.set(Expr.Tag.e_if, Tag.e_if); + list.set(Expr.Tag.e_require_or_require_resolve, Tag.e_require_or_require_resolve); + list.set(Expr.Tag.e_import, Tag.e_import); + list.set(Expr.Tag.e_this, Tag.e_this); + list.set(Expr.Tag.e_class, Tag.e_class); + list.set(Expr.Tag.e_require, Tag.e_require); + break :brk list; + }; + + pub const Validator = struct { + pub const List = std.EnumArray(JSNode.Tag, bool); + fn NewList(comptime valid_tags: anytype) List { + return comptime brk: { + var list = List.initFill(false); + for (std.meta.fieldNames(@TypeOf(valid_tags))) |index| { + const name = @tagName(@field(valid_tags, index)); + + if (!@hasField(JSNode.Tag, name)) { + @compileError( + "JSNode.Tag does not have a \"" ++ name ++ "\" field. Valid fields are " ++ std.fmt.comptimePrint( + "{s}", + .{ + std.meta.fieldNames(@TypeOf(valid_tags)), + }, + ), + ); + } + list.set(@field(JSNode.Tag, name), true); + } + + break :brk list; + }; + } + + pub const valid_object_tags = Tag.Validator.NewList(.{ + .g_property, + .e_spread, + .e_identifier, + .e_import_identifier, + .e_index, + .e_call, + .e_private_identifier, + .e_dot, + .e_unary, + .e_binary, + }); + }; + + pub const max_tag: u8 = brk: { + const Enum: std.builtin.TypeInfo.Enum = @typeInfo(Tag).Enum; + var max_value: u8 = 0; + for (Enum.fields) |field| { + max_value = std.math.max(@as(u8, field.value), max_value); + } + break :brk max_value; + }; + + pub const min_tag: u8 = brk: { + const Enum: std.builtin.TypeInfo.Enum = @typeInfo(Tag).Enum; + var min: u8 = 255; + for (Enum.fields) |field| { + min = std.math.min(@as(u8, field.value), min); + } + break :brk min; + }; + }; + + pub fn NewJSXWriter(comptime P: type) type { + return struct { + const JSXWriter = @This(); + p: *P, + bun_jsx_ref: Ref, + log: *logger.Log, + args: ExprList, + bun_identifier: *E.Identifier, + allocator: *std.mem.Allocator, + parent_tag: Tag = Tag.e_missing, + + pub fn initWriter(p: *P, bun_identifier: *E.Identifier) JSXWriter { + return JSXWriter{ + .p = p, + .log = p.log, + .bun_jsx_ref = p.bun_jsx_ref, + .args = ExprList.init(p.allocator), + .allocator = p.allocator, + .bun_identifier = bun_identifier, + }; + } + + fn hasPropertyNamed(props: []G.Property, comptime name: string) bool { + return indexOfPropertyByName(props, name) != null; + } + + fn indexOfPropertyByName(props: []G.Property, comptime name: string) ?u32 { + for (props) |prop, i| { + const key = prop.key orelse continue; + if (key.data != .e_string or !key.data.e_string.isUTF8()) continue; + if (strings.eqlComptime(key.data.e_string.utf8, name)) return @intCast(u32, i); + } + + return null; + } + + fn propertyValueNamed(props: []G.Property, comptime name: string) ?Expr { + for (props) |prop| { + const key = prop.key orelse continue; + if (key.data != .e_string or !key.data.e_string.isUTF8()) continue; + if (strings.eqlComptime(key.data.e_string.utf8, name)) return prop.value; + } + + return null; + } + + pub fn writeExprType(self: *JSXWriter, expr: Expr) bool {} + + pub fn writeNodeType(self: *JSXWriter, tag: JSNode.Tag, props: []G.Property, children: []Expr, loc: logger.Loc) bool { + switch (tag) { + + // <bool value={foo} /> + // intended for dynamic values + Tag.e_boolean => { + self.args.ensureUnusedCapacity(2) catch unreachable; + self.args.appendAssumeCapacity(Expr{ .loc = loc, .data = comptime Tag.ids.get(Tag.e_boolean) }); + const value_i = indexOfPropertyByName(props, "value") orelse { + self.log.addError(self.p.source, loc, "<bool> should have a \"value\" prop") catch unreachable; + self.args.append(Expr{ .data = .{ .e_boolean = .{ .value = true } }, .loc = loc }) catch unreachable; + return true; + }; + const value = props[value_i].value orelse Expr{ .data = .{ .e_boolean = .{ .value = true } }, .loc = loc }; + + switch (value.data) { + .e_jsx_element => |el| { + return self.writeElement(el.*); + }, + .e_string => { + self.log.addError(self.p.source, value.loc, "\"value\" shouldn't be a string") catch unreachable; + self.args.appendAssumeCapacity(Expr{ .data = .{ .e_boolean = .{ .value = true } }, .loc = value.loc }); + }, + .e_boolean => { + self.args.appendAssumeCapacity(value); + }, + .e_missing => { + self.args.appendAssumeCapacity(Expr{ .data = .{ .e_boolean = .{ .value = true } }, .loc = value.loc }); + }, + // null and undefined literals are coerced to false + .e_null, .e_undefined => { + self.args.appendAssumeCapacity(Expr{ .data = .{ .e_boolean = .{ .value = false } }, .loc = value.loc }); + }, + .e_number => { + // Numbers are cooerced to booleans + self.args.appendAssumeCapacity(Expr{ .data = .{ .e_boolean = .{ .value = value.data.e_number.value > 0.0 } }, .loc = value.loc }); + }, + // these ones are not statically analyzable so we just leave them in as-is + .e_if, .e_identifier, .e_import_identifier, .e_index, .e_call, .e_private_identifier, .e_dot, .e_unary, .e_binary => { + self.args.appendAssumeCapacity(self.p.visitExpr(value)); + }, + // everything else is invalid + else => { + self.log.addError(self.p.source, value.loc, "\"value\" should be a bool, jsx element, number, identifier, index, call, private identifier, or dot") catch unreachable; + self.args.appendAssumeCapacity(Expr{ .data = .{ .e_boolean = .{ .value = false } }, .loc = value.loc }); + }, + } + + return true; + }, + // <number value={1.0} /> + Tag.e_number => { + self.args.ensureUnusedCapacity(2) catch unreachable; + self.args.appendAssumeCapacity(Expr{ .loc = loc, .data = comptime Tag.ids.get(Tag.e_number) }); + const invalid_value = Expr{ .data = .{ .e_number = .{ .value = 0.0 } }, .loc = loc }; + const value_i = indexOfPropertyByName(props, "value") orelse { + self.log.addError(self.p.source, loc, "<number> should have a \"value\" prop") catch unreachable; + self.args.append(invalid_value) catch unreachable; + return true; + }; + const value = props[value_i].value orelse invalid_value; + + switch (value.data) { + .e_jsx_element => |el| { + return self.writeElement(el.*); + }, + .e_string => { + self.log.addError(self.p.source, loc, "<number> should not be a string.") catch unreachable; + self.args.appendAssumeCapacity(invalid_value); + }, + .e_boolean => { + // Booleans are cooerced to numbers + self.args.appendAssumeCapacity( + Expr{ + .data = .{ + .e_number = E.Number{ + .value = @intToFloat(f64, @boolToInt(value.data.e_boolean.value)), + }, + }, + .loc = value.loc, + }, + ); + }, + .e_missing => { + self.args.appendAssumeCapacity(invalid_value); + }, + // null and undefined literals are coerced to 0 + .e_null, .e_undefined => { + self.args.appendAssumeCapacity(Expr{ .data = .{ .e_number = .{ .value = 0 } }, .loc = value.loc }); + }, + // <number>123</number> + .e_number => { + // Numbers are cooerced to booleans + self.args.appendAssumeCapacity(value); + }, + // these ones are not statically analyzable so we just leave them in as-is + .e_if, .e_identifier, .e_import_identifier, .e_index, .e_call, .e_private_identifier, .e_dot, .e_unary, .e_binary => { + self.args.appendAssumeCapacity(self.p.visitExpr(value)); + }, + // everything else is invalid + else => { + self.log.addError(self.p.source, value.loc, "<number value> should be a number, jsx element, identifier, index, call, private identifier, or dot expression") catch unreachable; + self.args.appendAssumeCapacity(invalid_value); + }, + } + + return true; + }, + Tag.e_big_int => { + self.args.ensureUnusedCapacity(2) catch unreachable; + self.args.appendAssumeCapacity(Expr{ .loc = loc, .data = comptime Tag.ids.get(Tag.e_big_int) }); + const invalid_value = Expr{ .data = .{ .e_big_int = &E.BigInt.empty }, .loc = loc }; + const value_i = indexOfPropertyByName(props, "value") orelse { + self.log.addError(self.p.source, loc, "<big-int> should have a \"value\" prop") catch unreachable; + self.args.append(invalid_value) catch unreachable; + return true; + }; + const value = props[value_i].value orelse invalid_value; + + switch (value.data) { + .e_jsx_element => |el| { + return self.writeElement(el.*); + }, + .e_string => |str| { + self.args.appendAssumeCapacity(Expr.alloc(self.allocator, E.BigInt, E.BigInt{ .value = std.mem.trimRight(u8, str.utf8, "n") }, value.loc)); + }, + .e_big_int => |bigint| { + self.args.appendAssumeCapacity(value); + }, + .e_missing => { + self.args.appendAssumeCapacity(invalid_value); + }, + // null and undefined literals are coerced to 0 + .e_null, .e_undefined => { + self.args.appendAssumeCapacity(Expr{ .data = .{ .e_big_int = &E.BigInt.empty }, .loc = value.loc }); + }, + // these ones are not statically analyzable so we just leave them in as-is + .e_if, .e_identifier, .e_import_identifier, .e_index, .e_call, .e_private_identifier, .e_dot, .e_unary, .e_binary => { + self.args.appendAssumeCapacity(self.p.visitExpr(value)); + }, + // everything else is invalid + else => { + self.log.addError(self.p.source, value.loc, "\"value\" should be a BigInt, jsx element, identifier, index, call, private identifier, or dot expression") catch unreachable; + self.args.appendAssumeCapacity(invalid_value); + }, + } + + return true; + }, + Tag.e_array => { + self.args.ensureUnusedCapacity(2 + children.len) catch unreachable; + self.args.appendAssumeCapacity(Expr{ .loc = loc, .data = comptime Tag.ids.get(Tag.e_array) }); + const children_count = @truncate(u16, children.len); + self.args.appendAssumeCapacity(Expr{ .loc = loc, .data = .{ .e_number = E.Number{ .value = @intToFloat(f64, children_count) } } }); + + var old_parent = self.parent_tag; + self.parent_tag = Tag.e_array; + defer self.parent_tag = old_parent; + for (children) |child, i| { + switch (child.data) { + .e_jsx_element => |el| { + if (!self.writeElement(el.*)) return false; + }, + // TODO: handle when simplification changes the expr type + .e_spread, .e_identifier, .e_import_identifier, .e_index, .e_call, .e_private_identifier, .e_dot, .e_unary, .e_binary => { + const visited_expr = self.p.visitExpr(child); + switch (visited_expr.data) { + .e_jsx_element => |el| { + if (!self.writeElement(el.*)) return false; + }, + .e_if, .e_spread, .e_identifier, .e_import_identifier, .e_index, .e_call, .e_private_identifier, .e_dot, .e_unary, .e_binary => { + self.args.append(visited_expr) catch unreachable; + }, + else => { + self.log.addError(self.p.source, visited_expr.loc, "<array> should only contain other jsx elements") catch unreachable; + self.args.append(Expr{ .data = .{ .e_missing = E.Missing{} }, .loc = visited_expr.loc }) catch unreachable; + }, + } + }, + else => { + self.log.addError(self.p.source, child.loc, "<array> should only contain other jsx elements") catch unreachable; + self.args.append(Expr{ .data = .{ .e_missing = E.Missing{} }, .loc = child.loc }) catch unreachable; + }, + } + } + + return true; + }, + Tag.e_object => { + self.args.ensureUnusedCapacity(2 + children.len) catch unreachable; + self.args.appendAssumeCapacity(Expr{ .loc = loc, .data = comptime Tag.ids.get(Tag.e_object) }); + const children_count = @truncate(u16, children.len); + self.args.appendAssumeCapacity(Expr{ .loc = loc, .data = .{ .e_number = E.Number{ .value = @intToFloat(f64, children_count) } } }); + + var old_parent = self.parent_tag; + self.parent_tag = Tag.e_object; + defer self.parent_tag = old_parent; + + for (children) |child, i| { + switch (child.data) { + .e_jsx_element => |el| { + if (!self.writeElementWithValidTagList(el.*, comptime Tag.Validator.valid_object_tags)) return false; + }, + .e_if, .e_spread, .e_identifier, .e_import_identifier, .e_index, .e_call, .e_private_identifier, .e_dot, .e_unary, .e_binary => { + const visited = self.p.visitExpr(child); + switch (visited.data) { + .e_jsx_element => |el| { + if (!self.writeElementWithValidTagList(el.*, comptime Tag.Validator.valid_object_tags)) return false; + }, + .e_if, .e_spread, .e_identifier, .e_import_identifier, .e_index, .e_call, .e_private_identifier, .e_dot, .e_unary, .e_binary => { + self.args.append(visited) catch unreachable; + }, + else => { + self.log.addError(self.p.source, child.loc, "<object> should only contain other jsx elements") catch unreachable; + self.args.append(Expr{ .data = .{ .e_missing = E.Missing{} }, .loc = child.loc }) catch unreachable; + }, + } + }, + else => { + self.log.addError(self.p.source, child.loc, "<object> should only contain other jsx elements") catch unreachable; + self.args.append(Expr{ .data = .{ .e_missing = E.Missing{} }, .loc = child.loc }) catch unreachable; + }, + } + } + + return true; + }, + + Tag.g_property => { + const name_property = propertyValueNamed(props, "name"); + const value_property = propertyValueNamed(props, "value"); + const init_property = propertyValueNamed(props, "init"); + + var old_parent = self.parent_tag; + if (old_parent != .e_object) { + self.args.append(Expr{ .loc = loc, .data = comptime Tag.ids.get(Tag.g_property) }) catch unreachable; + } + + self.parent_tag = Tag.g_property; + defer self.parent_tag = old_parent; + + var is_spread = false; + if (value_property) |prop| { + switch (prop.data) { + .e_jsx_element => |el| { + if (!self.writeElement(el.*)) return false; + }, + .e_if, .e_spread, .e_identifier, .e_import_identifier, .e_index, .e_call, .e_private_identifier, .e_dot, .e_unary, .e_binary => { + self.args.append(self.p.visitExpr(prop)) catch unreachable; + }, + else => { + self.log.addError(self.p.source, prop.loc, "value should only contain other jsx elements") catch unreachable; + self.args.append(Expr{ .data = .{ .e_missing = E.Missing{} }, .loc = prop.loc }) catch unreachable; + }, + } + } else { + self.args.append(Expr{ .data = comptime Tag.ids.get(.e_undefined), .loc = loc }) catch unreachable; + } + + if (init_property) |prop| { + switch (prop.data) { + .e_jsx_element => |el| { + if (!self.writeElement(el.*)) return false; + }, + + .e_spread, .e_identifier, .e_import_identifier, .e_index, .e_call, .e_private_identifier, .e_dot, .e_unary, .e_binary => { + self.args.append(self.p.visitExpr(prop)) catch unreachable; + }, + else => { + self.log.addError(self.p.source, prop.loc, "init should only contain other jsx elements") catch unreachable; + self.args.append(Expr{ .data = .{ .e_missing = E.Missing{} }, .loc = prop.loc }) catch unreachable; + }, + } + } else { + self.args.append(Expr{ .data = comptime Tag.ids.get(.e_undefined), .loc = loc }) catch unreachable; + } + + if (name_property) |prop| { + switch (prop.data) { + .e_jsx_element => |el| { + if (!self.writeElement(el.*)) return false; + }, + .e_string => |str| { + self.args.append(prop) catch unreachable; + }, + .e_if, .e_spread, .e_identifier, .e_import_identifier, .e_index, .e_call, .e_private_identifier, .e_dot, .e_unary, .e_binary => { + self.args.append(self.p.visitExpr(prop)) catch unreachable; + }, + else => { + self.log.addError(self.p.source, prop.loc, "should only contain other jsx elements or a string") catch unreachable; + self.args.append(Expr{ .data = .{ .e_missing = E.Missing{} }, .loc = prop.loc }) catch unreachable; + }, + } + } + + return true; + }, + Tag.e_string => { + self.args.ensureUnusedCapacity(2) catch unreachable; + self.args.appendAssumeCapacity(Expr{ .loc = loc, .data = comptime Tag.ids.get(Tag.e_string) }); + const invalid_value = Expr{ .data = .{ .e_string = &E.String.empty }, .loc = loc }; + const value_i = indexOfPropertyByName(props, "value") orelse { + self.log.addError(self.p.source, loc, "<string> should have a \"value\" prop") catch unreachable; + self.args.append(invalid_value) catch unreachable; + return true; + }; + const value = props[value_i].value orelse invalid_value; + + switch (value.data) { + .e_jsx_element => |el| { + return self.writeElement(el.*); + }, + .e_string => { + self.args.appendAssumeCapacity(value); + }, + .e_missing => { + self.args.appendAssumeCapacity(invalid_value); + }, + // null is cooerced to "null" + .e_null => { + self.args.appendAssumeCapacity(Expr{ .loc = value.loc, .data = .{ .e_string = &E.String.@"null" } }); + }, + // undefined is cooerced to "undefined" + .e_undefined => { + self.args.appendAssumeCapacity(Expr{ .loc = value.loc, .data = .{ .e_string = &E.String.@"undefined" } }); + }, + .e_boolean => |boolean| { + self.args.appendAssumeCapacity(Expr{ .loc = value.loc, .data = .{ .e_string = if (boolean.value) &E.String.@"true" else &E.String.@"false" } }); + }, + // these ones are not statically analyzable so we just leave them in as-is + .e_if, .e_identifier, .e_import_identifier, .e_index, .e_call, .e_private_identifier, .e_dot, .e_unary, .e_binary => { + self.args.appendAssumeCapacity(self.p.visitExpr(value)); + }, + // everything else is invalid + else => { + self.log.addError(self.p.source, value.loc, "<string value> should be a string, jsx element, identifier, index, call, private identifier, or dot expression") catch unreachable; + self.args.appendAssumeCapacity(invalid_value); + }, + } + }, + Tag.e_reg_exp => { + self.args.ensureUnusedCapacity(2) catch unreachable; + self.args.appendAssumeCapacity(Expr{ .loc = loc, .data = comptime Tag.ids.get(Tag.e_reg_exp) }); + const invalid_value = Expr{ .data = .{ .e_reg_exp = &E.RegExp.empty }, .loc = loc }; + + const value_i = indexOfPropertyByName(props, "value") orelse { + self.log.addError(self.p.source, loc, "<regex> should have a \"value\" prop") catch unreachable; + self.args.append(invalid_value) catch unreachable; + return true; + }; + + const value = props[value_i].value orelse invalid_value; + + switch (value.data) { + .e_string => |str| { + self.args.appendAssumeCapacity(Expr.alloc(self.allocator, E.RegExp, E.RegExp{ .value = str.utf8 }, value.loc)); + }, + .e_reg_exp => { + self.args.appendAssumeCapacity(value); + }, + .e_missing, .e_null, .e_undefined => { + self.args.appendAssumeCapacity(invalid_value); + }, + // these ones are not statically analyzable so we just leave them in as-is + .e_if, .e_identifier, .e_import_identifier, .e_index, .e_call, .e_private_identifier, .e_dot, .e_unary, .e_binary => { + self.args.appendAssumeCapacity(self.p.visitExpr(value)); + }, + // everything else is invalid + else => { + self.log.addError(self.p.source, value.loc, "<regex value> should be a string, jsx element, identifier, index, call, private identifier, or dot expression") catch unreachable; + self.args.appendAssumeCapacity(invalid_value); + }, + } + + return true; + }, + // Tag.e_jsx_element => unreachable, + // Tag.e_identifier => { + // // self.args.ensureUnusedCapacity(2) catch unreachable; + // Global.notimpl(); + // }, + // Tag.e_import_identifier => { + // Global.notimpl(); + // }, + // Tag.e_private_identifier => { + // Global.notimpl(); + // }, + + // Tag.e_unary => { + + // }, + // Tag.e_binary => {}, + + // Tag.e_function => {}, + // Tag.e_new_target => {}, + // Tag.e_import_meta => {}, + // Tag.e_call => {}, + // Tag.e_dot => {}, + // Tag.e_index => {}, + // Tag.e_arrow => {}, + + // Tag.e_spread => {}, + + // Tag.e_template_part => {}, + // Tag.e_template => {}, + // Tag.e_regex => {}, + // Tag.e_await => {}, + // Tag.e_yield => {}, + // Tag.e_if => {}, + // Tag.e_import => {}, + + // Tag.e_class => {}, + // Tag.e_require => {}, + // Tag.s_import => {}, + + // Tag.s_block => {}, + + // The valueless ones + Tag.e_super, Tag.e_null, Tag.e_undefined, Tag.e_missing, Tag.inline_true, Tag.inline_false, Tag.e_this => { + self.args.append(Expr{ .loc = loc, .data = Tag.ids.get(tag) }) catch unreachable; + }, + else => Global.panic("Tag \"{s}\" is not implemented yet.", .{@tagName(tag)}), + } + + return true; + } + + pub fn writeFunctionCall(self: *JSXWriter, element: E.JSXElement) Expr { + if (element.tag) |tag_expr| { + switch (tag_expr.data) { + .e_string => |str| { + self.p.recordUsage(self.bun_jsx_ref); + _ = self.writeElement(element); + var call_args = self.p.allocator.alloc(Expr, 1) catch unreachable; + call_args[0] = Expr.alloc(self.p.allocator, E.Array, E.Array{ .items = self.args.items }, tag_expr.loc); + + return Expr.alloc( + self.p.allocator, + E.Call, + E.Call{ + .target = Expr{ + .data = .{ + .e_identifier = self.bun_identifier, + }, + .loc = tag_expr.loc, + }, + .can_be_unwrapped_if_unused = true, + .args = call_args, + }, + tag_expr.loc, + ); + }, + else => Global.panic("Not implemented yet top-level jsx element: {s}", .{@tagName(tag_expr.data)}), + } + } + + return Expr{ .data = .{ .e_missing = .{} }, .loc = logger.Loc.Empty }; + } + + pub fn writeRootElement(self: JSXWriter, element: E.JSXElement) Expr { + var tag = element.tag orelse E.Array{ .items = &.{} }; + switch (tag.data) { + .e_string, .e_array => {}, + else => {}, + } + } + + fn writeElementWithValidTagList(self: *JSXWriter, element: E.JSXElement, comptime valid_tags: Tag.Validator.List) bool { + const tag_expr = element.tag orelse return false; + if (tag_expr.data != .e_string) return false; + const str = tag_expr.data.e_string; + var p = self.p; + + const node_type: JSNode.Tag = JSNode.Tag.names.get(str.utf8) orelse { + if (!str.isUTF8()) { + self.log.addErrorFmt(p.source, tag_expr.loc, p.allocator, "Tag \"{s}\" is invalid", .{strings.toUTF8Alloc(self.p.allocator, str.value)}) catch unreachable; + } else { + self.log.addErrorFmt(p.source, tag_expr.loc, p.allocator, "Tag \"{s}\" is invalid", .{str.utf8}) catch unreachable; + } + return false; + }; + + if (!valid_tags.get(node_type)) { + self.log.addErrorFmt(p.source, tag_expr.loc, p.allocator, "Tag \"{s}\" is invalid here", .{str.utf8}) catch unreachable; + } + + return self.writeNodeType(node_type, element.properties, element.children, tag_expr.loc); + } + + pub fn writeElement(self: *JSXWriter, element: E.JSXElement) bool { + const tag_expr = element.tag orelse return false; + if (tag_expr.data != .e_string) return false; + const str = tag_expr.data.e_string; + var p = self.p; + + const node_type: JSNode.Tag = JSNode.Tag.names.get(str.utf8) orelse { + if (!str.isUTF8()) { + self.log.addErrorFmt(p.source, tag_expr.loc, p.allocator, "Tag \"{s}\" is invalid", .{strings.toUTF8Alloc(self.p.allocator, str.value)}) catch unreachable; + } else { + self.log.addErrorFmt(p.source, tag_expr.loc, p.allocator, "Tag \"{s}\" is invalid", .{str.utf8}) catch unreachable; + } + return false; + }; + + return self.writeNodeType(node_type, element.properties, element.children, tag_expr.loc); + } + }; + } + + pub const Writer = struct { + log: *logger.Log, + exception: JSCBase.ExceptionValueRef = null, + ctx: js.JSContextRef, + errored: bool = false, + allocator: *std.mem.Allocator, + loc: logger.Loc, + args_value: JSC.JSValue, + args_i: u32 = 0, + args_len: u32 = 0, + + pub inline fn eatArg(this: *Writer) ?JSC.JSValue { + if (this.args_i >= this.args_len) return null; + const i = this.args_i; + this.args_i += 1; + return JSC.JSObject.getIndex(this.args_value, JavaScript.VirtualMachine.vm.global, i); + } + + pub inline fn peekArg(this: *Writer) ?JSC.JSValue { + if (this.args_i >= this.args_len) return null; + return JSC.JSObject.getIndex(this.args_value, JavaScript.VirtualMachine.vm.global, this.args_i); + } + + pub inline fn nextJSValue(this: *Writer) ?JSC.JSValue { + return this.eatArg(); + } + + pub const TagOrJSNode = union(TagOrNodeType) { + tag: JSNode.Tag, + node: JSNode, + invalid: void, + + pub const TagOrNodeType = enum { + tag, + node, + invalid, + }; + + pub fn fromJSValueRefNoValidate(ctx: js.JSContextRef, value: js.JSValueRef) TagOrJSNode { + switch (js.JSValueGetType(ctx, value)) { + js.JSType.kJSTypeNumber => { + const tag_int = @floatToInt(u8, JSC.JSValue.fromRef(value).asNumber()); + if (tag_int < Tag.min_tag or tag_int > Tag.max_tag) { + return TagOrJSNode{ .invalid = .{} }; + } + return TagOrJSNode{ .tag = @intToEnum(JSNode.Tag, tag_int) }; + }, + js.JSType.kJSTypeObject => { + if (JSCBase.GetJSPrivateData(JSNode, value)) |node| { + return TagOrJSNode{ .node = node.* }; + } + + return TagOrJSNode{ .invalid = .{} }; + }, + else => { + return TagOrJSNode{ .invalid = .{} }; + }, + } + } + + pub fn fromJSValueRef(writer: *Writer, ctx: js.JSContextRef, value: js.JSValueRef) TagOrJSNode { + switch (js.JSValueGetType(ctx, value)) { + js.JSType.kJSTypeNumber => { + const tag_int = @floatToInt(u8, JSC.JSValue.fromRef(value).asNumber()); + if (tag_int < Tag.min_tag or tag_int > Tag.max_tag) { + throwTypeError(ctx, "Node type has invalid value", writer.exception); + writer.errored = true; + return TagOrJSNode{ .invalid = .{} }; + } + return TagOrJSNode{ .tag = @intToEnum(JSNode.Tag, tag_int) }; + }, + js.JSType.kJSTypeObject => { + if (JSCBase.GetJSPrivateData(JSNode, value)) |node| { + return TagOrJSNode{ .node = node.* }; + } + + return TagOrJSNode{ .invalid = .{} }; + }, + else => { + throwTypeError(writer.ctx, "Invalid Bun AST", writer.exception); + return TagOrJSNode{ .invalid = .{} }; + }, + } + } + + pub fn fromJSValue(writer: *Writer, value: JSC.JSValue) TagOrJSNode { + return fromJSValueRef(writer, JavaScript.VirtualMachine.vm.global.ref(), value.asRef()); + } + }; + + fn writeProperty(writer: *Writer, property: *G.Property) bool { + + // Property is + // value + // initializer + // if property value is an e.spread, then key is skipped + // key + + // value is first + var expect_key = true; + switch (TagOrJSNode.fromJSValue(writer, writer.eatArg() orelse return false)) { + TagOrJSNode.tag => |tag| { + var expr: Expr = Expr{ .loc = writer.loc, .data = .{ .e_null = E.Null{} } }; + + if (!writer.writeFromJSWithTagInExpr(tag, &expr)) return false; + property.value = switch (expr.data) { + .e_missing, .e_undefined => null, + else => expr, + }; + property.flags.is_spread = expr.data == .e_spread; + expect_key = property.value == null or !property.flags.is_spread; + }, + TagOrJSNode.node => |node| { + const expr = node.toExpr(); + property.value = switch (expr.data) { + .e_missing, .e_undefined => null, + else => expr, + }; + property.flags.is_spread = expr.data == .e_spread; + expect_key = property.value == null or !property.flags.is_spread; + }, + TagOrJSNode.invalid => { + return false; + }, + } + + switch (TagOrJSNode.fromJSValue(writer, writer.eatArg() orelse return false)) { + TagOrJSNode.tag => |tag| { + var expr: Expr = Expr{ .loc = writer.loc, .data = .{ .e_null = E.Null{} } }; + + if (!writer.writeFromJSWithTagInExpr(tag, &expr)) return false; + property.initializer = switch (expr.data) { + .e_missing, .e_undefined => null, + else => expr, + }; + }, + TagOrJSNode.node => |node| { + const expr = node.toExpr(); + property.initializer = switch (expr.data) { + .e_missing, .e_undefined => null, + else => expr, + }; + }, + TagOrJSNode.invalid => { + return false; + }, + } + + if (expect_key) { + var next_arg = writer.peekArg() orelse return false; + // its okay for property keys to literally be strings + // <property name="foo"> + if (next_arg.isString()) { + var expr: Expr = Expr{ .loc = writer.loc, .data = .{ .e_string = &E.String.empty } }; + if (!writer.writeFromJSWithTagInExpr(JSNode.Tag.e_string, &expr)) return false; + property.key = expr; + } else { + switch (TagOrJSNode.fromJSValue(writer, writer.eatArg() orelse return false)) { + TagOrJSNode.tag => |tag| { + var expr: Expr = Expr{ .loc = writer.loc, .data = .{ .e_null = E.Null{} } }; + if (!writer.writeFromJSWithTagInExpr(tag, &expr)) return false; + property.key = expr; + }, + TagOrJSNode.node => |node| { + property.key = node.toExpr(); + }, + TagOrJSNode.invalid => { + return false; + }, + } + } + } + + return true; + } + + fn writeFromJSWithTagInExpr(writer: *Writer, tag: JSNode.Tag, expr: *Expr) bool { + switch (tag) { + .e_array => { + // var e_array: E.Array = E.Array{ .items = writer.allocator.alloc(E.Array, args.len) catch return false }; + var count = (writer.nextJSValue() orelse return false).toU16(); + var i: u16 = 0; + var items = ExprList.initCapacity(writer.allocator, count) catch unreachable; + + while (i < count) { + var nextArg = writer.eatArg() orelse return false; + if (js.JSValueIsArray(writer.ctx, nextArg.asRef())) { + const extras = nextArg.getLengthOfArray(JavaScript.VirtualMachine.vm.global); + count += std.math.max(@truncate(u16, extras), 1) - 1; + items.ensureUnusedCapacity(extras) catch unreachable; + items.expandToCapacity(); + var new_writer = writer.*; + new_writer.args_i = 0; + new_writer.args_len = extras; + new_writer.args_value = nextArg; + + while (new_writer.nextJSValue()) |value| { + defer i += 1; + switch (TagOrJSNode.fromJSValue(&new_writer, value)) { + TagOrJSNode.tag => |tag_| { + if (!new_writer.writeFromJSWithTagInExpr( + tag_, + &items.items[i], + )) return false; + }, + TagOrJSNode.node => |node_| { + const node: JSNode = node_; + switch (node.data) { + JSNode.Tag.s_import => |import| { + return false; + }, + else => { + items.items[i] = node.toExpr(); + }, + } + }, + TagOrJSNode.invalid => { + return false; + }, + } + } + } else { + defer i += 1; + + switch (TagOrJSNode.fromJSValue(writer, nextArg)) { + TagOrJSNode.tag => |tag_| { + if (!writer.writeFromJSWithTagInExpr(tag_, &items.items[i])) return false; + }, + TagOrJSNode.node => |node_| { + const node: JSNode = node_; + switch (node.data) { + JSNode.Tag.s_import => |import| { + return false; + }, + else => { + items.items[i] = node.toExpr(); + }, + } + }, + TagOrJSNode.invalid => { + return false; + }, + } + } + } + expr.* = Expr.alloc(writer.allocator, E.Array, E.Array{ .items = items.items[0..i] }, writer.loc); + return true; + }, + .e_boolean => { + expr.* = Expr{ .loc = writer.loc, .data = .{ .e_boolean = .{ + .value = JSC.JSValue.toBoolean(writer.nextJSValue() orelse return false), + } } }; + return true; + }, + .inline_true => { + expr.* = Expr{ .loc = writer.loc, .data = .{ .e_boolean = .{ .value = true } } }; + return true; + }, + .inline_false => { + expr.* = Expr{ .loc = writer.loc, .data = .{ .e_boolean = .{ .value = false } } }; + return true; + }, + .e_null => { + expr.* = Expr{ .loc = writer.loc, .data = .{ .e_null = E.Null{} } }; + return true; + }, + .e_undefined => { + expr.* = Expr{ .loc = writer.loc, .data = .{ .e_undefined = E.Undefined{} } }; + return true; + }, + .e_number => { + expr.* = Expr{ + .loc = writer.loc, + .data = .{ + .e_number = .{ + .value = JSC.JSValue.asNumber(writer.nextJSValue() orelse return false), + }, + }, + }; + return true; + }, + .e_string => { + var wtf_string = JSC.JSValue.toWTFString(writer.nextJSValue() orelse return false, JavaScript.VirtualMachine.vm.global); + if (wtf_string.isEmpty()) { + expr.* = Expr{ + .loc = writer.loc, + .data = .{ + .e_string = &E.String.empty, + }, + }; + } else if (wtf_string.is8Bit()) { + expr.* = Expr.alloc(writer.allocator, E.String, E.String{ .utf8 = wtf_string.characters8()[0..wtf_string.length()] }, writer.loc); + } else if (wtf_string.is16Bit()) { + expr.* = Expr.alloc(writer.allocator, E.String, E.String{ .value = wtf_string.characters16()[0..wtf_string.length()] }, writer.loc); + } else { + unreachable; + } + return true; + }, + .e_reg_exp => { + var jsstring = js.JSValueToStringCopy(writer.ctx, (writer.eatArg() orelse return false).asRef(), writer.exception); + defer js.JSStringRelease(jsstring); + + const len = js.JSStringGetLength(jsstring); + var str = writer.allocator.alloc(u8, len + 1) catch unreachable; + const outlen = js.JSStringGetUTF8CString(jsstring, str.ptr, len + 1); + expr.* = Expr.alloc(writer.allocator, E.RegExp, E.RegExp{ .value = str[0..outlen] }, writer.loc); + return true; + }, + .e_object => { + const len = (writer.nextJSValue() orelse return false).toU16(); + + var properties = writer.allocator.alloc(G.Property, len) catch return false; + var property_i: u16 = 0; + + while (property_i < properties.len) : (property_i += 1) { + switch (TagOrJSNode.fromJSValue(writer, writer.eatArg() orelse return false)) { + TagOrJSNode.tag => |tag_| { + if (tag_ != JSNode.Tag.g_property) return false; + + if (!writer.writeProperty( + &properties[property_i], + )) return false; + }, + TagOrJSNode.node => |node_| { + const node: JSNode = node_; + switch (node.data) { + .g_property => |property| { + properties[property_i] = property.*; + }, + else => { + return false; + }, + } + }, + TagOrJSNode.invalid => { + return false; + }, + } + } + + return true; + }, + else => { + return false; + }, + + // .e_call => {}, + + // .e_dot => {}, + // .e_index => {}, + // .e_identifier => {}, + // .e_import_identifier => {}, + + // .e_spread => {}, + + // .e_template_part => {}, + // .e_template => {}, + + // .e_await => {}, + // .e_yield => {}, + // .e_if => {}, + // .e_import => {}, + // .e_this => {}, + // .e_class => {}, + // s_import => {}, + } + + return false; + } + + pub fn writeFromJS(writer: *Writer) ?JSNode { + switch (TagOrJSNode.fromJSValueRef(writer, writer.ctx, (writer.eatArg() orelse return null).asRef())) { + TagOrJSNode.tag => |tag| { + var expr: Expr = Expr{ .loc = writer.loc, .data = .{ .e_null = E.Null{} } }; + + if (!writer.writeFromJSWithTagInExpr(tag, &expr)) return null; + return JSNode.initExpr(expr); + }, + TagOrJSNode.node => |node| { + return node; + }, + TagOrJSNode.invalid => { + return null; + }, + } + } + }; + // pub fn isInstanceOf( // ctx: js.JSContextRef, // obj: js.JSObjectRef, @@ -4245,71 +6116,100 @@ pub const Macro = struct { // js.JSValueToNumber(ctx, value, exception); // } - pub fn toString( - this: *JSExpr, + fn throwTypeError(ctx: js.JSContextRef, comptime msg: string, exception: js.ExceptionRef) void { + JSCBase.JSError(JSCBase.getAllocator(ctx), msg, .{}, ctx, exception); + } + + pub const BunJSXCallbackFunction = JSCBase.NewClass( + void, + .{ .name = "bunJSX" }, + .{ + .call = .{ + .rfn = createFromJavaScript, + .ro = true, + }, + .isNodeType = .{ + .rfn = isNodeType, + .ro = true, + }, + }, + .{}, + ); + + pub fn isNodeType( + this: void, ctx: js.JSContextRef, function: js.JSObjectRef, thisObject: js.JSObjectRef, arguments: []const js.JSValueRef, exception: js.ExceptionRef, ) js.JSObjectRef { - switch (this.expr.data) { - .e_string => |str| { - if (str.isBlank()) { - return JSC.ZigString.init("").toValue(JavaScript.VirtualMachine.vm.global).asRef(); - } + if (arguments.len != 2) { + throwTypeError(ctx, "bunJSX.isNodeType() requires 2 arguments", exception); + return null; + } - if (str.isUTF8()) { - return JSC.ZigString.init(str.utf8).toValue(JavaScript.VirtualMachine.vm.global).asRef(); - } else { - return js.JSValueMakeString(ctx, js.JSStringCreateWithCharactersNoCopy(str.value.ptr, str.value.len)); - } - }, - .e_template => |template| { - const str = template.head; + const TagOrNodeType = Writer.TagOrJSNode.TagOrNodeType; - if (str.isBlank()) { - return JSC.ZigString.init("").toValue(JavaScript.VirtualMachine.vm.global).asRef(); - } + const left = Writer.TagOrJSNode.fromJSValueRefNoValidate(ctx, arguments[0]); + const right = Writer.TagOrJSNode.fromJSValueRefNoValidate(ctx, arguments[1]); - if (str.isUTF8()) { - return JSC.ZigString.init(str.utf8).toValue(JavaScript.VirtualMachine.vm.global).asRef(); - } else { - return js.JSValueMakeString(ctx, js.JSStringCreateWithCharactersNoCopy(str.value.ptr, str.value.len)); - } - }, - else => { - return JSC.ZigString.init("").toValue(JavaScript.VirtualMachine.vm.global).asRef(); - }, + if (left == TagOrNodeType.invalid or right == TagOrNodeType.invalid) { + return js.JSValueMakeBoolean(ctx, false); } - } - pub fn getTag( - this: *JSExpr, - ctx: js.JSContextRef, - thisObject: js.JSValueRef, - prop: js.JSStringRef, - exception: js.ExceptionRef, - ) js.JSObjectRef { - return JSC.JSValue.jsNumberFromU16(@intCast(u16, @enumToInt(std.meta.activeTag(this.expr.data)))).asRef(); - } - pub fn getTagName( - this: *JSExpr, - ctx: js.JSContextRef, - thisObject: js.JSValueRef, - prop: js.JSStringRef, - exception: js.ExceptionRef, - ) js.JSObjectRef { - return JSC.ZigString.init(@tagName(this.expr.data)).toValue(JavaScript.VirtualMachine.vm.global).asRef(); + if (left == TagOrNodeType.node and right == TagOrNodeType.node) { + return js.JSValueMakeBoolean(ctx, @as(Tag, left.node.data) == @as(Tag, right.node.data)); + } + + if (left == TagOrNodeType.node) { + return js.JSValueMakeBoolean(ctx, @as(Tag, left.node.data) == right.tag); + } + + if (right == TagOrNodeType.node) { + return js.JSValueMakeBoolean(ctx, @as(Tag, right.node.data) == left.tag); + } + + unreachable; } - pub fn getPosition( - this: *JSExpr, + + pub fn createFromJavaScript( + this: void, ctx: js.JSContextRef, - thisObject: js.JSValueRef, - prop: js.JSStringRef, + function: js.JSObjectRef, + thisObject: js.JSObjectRef, + arguments: []const js.JSValueRef, exception: js.ExceptionRef, ) js.JSObjectRef { - return JSC.JSValue.jsNumberFromInt32(this.expr.loc.start).asRef(); + if (arguments.len != 1 or !js.JSValueIsArray(ctx, arguments[0])) { + throwTypeError(ctx, "bunJSX requires one array argument", exception); + return null; + } + + js.JSValueProtect(ctx, arguments[0]); + defer Output.flush(); + const args_value = JSC.JSValue.fromRef(arguments[0]); + var writer = Writer{ + .log = JavaScript.VirtualMachine.vm.log, + .ctx = ctx, + .loc = logger.Loc.Empty, + .allocator = JSCBase.getAllocator(ctx), + .exception = exception, + .args_value = args_value, + .args_len = args_value.getLengthOfArray(JavaScript.VirtualMachine.vm.global), + .args_i = 0, + .errored = false, + }; + + if (writer.writeFromJS()) |node| { + var ptr = writer.allocator.create(JSNode) catch unreachable; + ptr.* = node; + var result = JSNode.Class.make(ctx, ptr); + js.JSValueProtect(ctx, result); + return result; + } + + return null; } }; @@ -4366,8 +6266,8 @@ pub const Macro = struct { } pub const Runner = struct { - threadlocal var args_buf: [32]js.JSObjectRef = undefined; - threadlocal var expr_nodes_buf: [32]JSExpr = undefined; + threadlocal var args_buf: [2]js.JSObjectRef = undefined; + threadlocal var expr_nodes_buf: [1]JSNode = undefined; threadlocal var exception_holder: Zig.ZigException.Holder = undefined; pub fn run( macro: Macro, @@ -4382,23 +6282,18 @@ pub const Macro = struct { if (comptime isDebug) Output.prettyln("<r><d>[macro]<r> call <d><b>{s}<r>", .{function_name}); exception_holder = Zig.ZigException.Holder.init(); - expr_nodes_buf[0] = JSExpr{ .expr = caller }; - args_buf[0] = JSExpr.Class.make( + expr_nodes_buf[0] = JSNode.initExpr(caller); + args_buf[0] = JSNode.Class.make( macro.vm.global.ref(), &expr_nodes_buf[0], ); - for (args) |arg, i| { - expr_nodes_buf[i + 1] = JSExpr{ .expr = arg }; - args_buf[i + 1] = - JSExpr.Class.make( - macro.vm.global.ref(), - &expr_nodes_buf[i + 1], - ); - } - args_buf[args.len + 2] = null; + + args_buf[1] = null; var macro_callback = macro.vm.macros.get(id) orelse return caller; var result = js.JSObjectCallAsFunctionReturnValue(macro.vm.global.ref(), macro_callback, null, args.len + 1, &args_buf); + js.JSValueProtect(macro.vm.global.ref(), result.asRef()); + defer js.JSValueUnprotect(macro.vm.global.ref(), result.asRef()); var promise = JSC.JSPromise.resolvedPromise(macro.vm.global, result); macro.vm.global.vm().drainMicrotasks(); @@ -4409,7 +6304,11 @@ pub const Macro = struct { const value = promise.result(macro.vm.global.vm()); - return caller; + if (JSCBase.GetJSPrivateData(JSNode, value.asObjectRef())) |node| { + return node.toExpr(); + } else { + return Expr{ .data = .{ .e_missing = .{} }, .loc = caller.loc }; + } } }; }; diff --git a/src/js_lexer.zig b/src/js_lexer.zig index ec800cfb5..91f4fceef 100644 --- a/src/js_lexer.zig +++ b/src/js_lexer.zig @@ -76,6 +76,7 @@ pub const Lexer = struct { number: f64 = 0.0, rescan_close_brace_as_template_token: bool = false, prev_error_loc: logger.Loc = logger.Loc.Empty, + regex_flags_start: ?u16 = null, allocator: *std.mem.Allocator, /// In JavaScript, strings are stored as UTF-16, but nearly every string is ascii. /// This means, usually, we can skip UTF8 -> UTF16 conversions. @@ -108,6 +109,7 @@ pub const Lexer = struct { .all_original_comments = self.all_original_comments, .code_point = self.code_point, .identifier = self.identifier, + .regex_flags_start = self.regex_flags_start, .jsx_factory_pragma_comment = self.jsx_factory_pragma_comment, .jsx_fragment_pragma_comment = self.jsx_fragment_pragma_comment, .source_mapping_url = self.source_mapping_url, @@ -1756,13 +1758,21 @@ pub const Lexer = struct { } pub fn scanRegExp(lexer: *LexerType) !void { + lexer.regex_flags_start = null; while (true) { switch (lexer.code_point) { '/' => { try lexer.step(); + + var has_set_flags_start = false; while (isIdentifierContinue(lexer.code_point)) { switch (lexer.code_point) { 'g', 'i', 'm', 's', 'u', 'y' => { + if (!has_set_flags_start) { + lexer.regex_flags_start = @truncate(u16, lexer.end - lexer.start); + has_set_flags_start = true; + } + try lexer.step(); }, else => { diff --git a/src/js_parser/js_parser.zig b/src/js_parser/js_parser.zig index 916e3e061..c73ae5718 100644 --- a/src/js_parser/js_parser.zig +++ b/src/js_parser/js_parser.zig @@ -22,6 +22,9 @@ const JSXFactoryName = "JSX"; const JSXAutomaticName = "jsx_module"; const MacroRefs = std.AutoArrayHashMap(Ref, u32); +const BunJSX = struct { + pub threadlocal var bun_jsx_identifier: E.Identifier = undefined; +}; pub fn ExpressionTransposer( comptime Kontext: type, visitor: fn (ptr: *Kontext, arg: Expr, state: anytype) Expr, @@ -88,6 +91,12 @@ pub const ImportScanner = struct { .s_import => |st| { var record: *ImportRecord = &p.import_records.items[st.import_record_index]; + if (strings.eqlComptime(record.path.namespace, "macro")) { + record.is_unused = true; + record.path.is_disabled = true; + continue; + } + // The official TypeScript compiler always removes unused imported // symbols. However, we deliberately deviate from the official // TypeScript compiler's behavior doing this in a specific scenario: @@ -1767,7 +1776,7 @@ pub const Parser = struct { ts: bool = false, keep_names: bool = true, omit_runtime_for_tests: bool = false, - ignore_dce_annotations: bool = true, + ignore_dce_annotations: bool = false, preserve_unused_imports_ts: bool = false, use_define_for_class_fields: bool = false, suppress_warnings_about_weird_code: bool = true, @@ -1846,9 +1855,10 @@ pub const Parser = struct { // - import 'foo'; // - import("foo") // - require("foo") - import_record.is_unused = import_record.kind == .stmt and + import_record.is_unused = import_record.is_unused or + (import_record.kind == .stmt and !import_record.was_originally_bare_import and - !import_record.calls_run_time_re_export_fn; + !import_record.calls_run_time_re_export_fn); } var iter = scan_pass.used_symbols.iterator(); @@ -1996,7 +2006,7 @@ pub const Parser = struct { } // Auto-import JSX - if (p.options.jsx.parse) { + if (ParserType.jsx_transform_type == .react) { const jsx_filename_symbol = p.symbols.items[p.jsx_filename.ref.inner_index]; { @@ -2693,8 +2703,9 @@ const ImportItemForNamespaceMap = std.StringArrayHashMap(LocRef); pub fn NewParser( comptime js_parser_features: ParserFeatures, ) type { + const js_parser_jsx = if (FeatureFlags.force_macro) JSXTransformType.macro else js_parser_features.jsx; const is_typescript_enabled = js_parser_features.typescript; - const is_jsx_enabled = js_parser_features.jsx != .none; + const is_jsx_enabled = js_parser_jsx != .none; const only_scan_imports_and_do_not_visit = js_parser_features.scan_only; const is_react_fast_refresh_enabled = js_parser_features.react_fast_refresh; @@ -2706,7 +2717,7 @@ pub fn NewParser( // public only because of Binding.ToExpr return struct { const P = @This(); - pub const jsx_transform_type: JSXTransformType = js_parser_features.jsx; + pub const jsx_transform_type: JSXTransformType = js_parser_jsx; macro_refs: MacroRefs = undefined, allocator: *std.mem.Allocator, options: Parser.Options, @@ -2796,6 +2807,8 @@ pub fn NewParser( // only applicable when is_react_fast_refresh_enabled jsx_refresh_runtime: GeneratedSymbol = GeneratedSymbol{ .ref = Ref.None, .primary = Ref.None, .backup = Ref.None }, + bun_jsx_ref: Ref = Ref.None, + // Imports (both ES6 and CommonJS) are tracked at the top level import_records: ImportRecordList, import_records_for_current_part: List(u32), @@ -3627,6 +3640,12 @@ pub fn NewParser( } }, .macro => { + p.bun_jsx_ref = p.declareSymbol(.other, logger.Loc.Empty, "bunJSX") catch unreachable; + BunJSX.bun_jsx_identifier = E.Identifier{ + .ref = p.bun_jsx_ref, + .can_be_removed_if_unused = true, + .call_can_be_unwrapped_if_unused = true, + }; p.jsx_fragment = p.declareGeneratedSymbol(.other, "Fragment") catch unreachable; }, else => {}, @@ -6150,6 +6169,7 @@ pub fn NewParser( p.import_records.items[stmt.import_record_index].path.namespace = js_ast.Macro.namespace; if (comptime only_scan_imports_and_do_not_visit) { p.import_records.items[stmt.import_record_index].path.is_disabled = true; + p.import_records.items[stmt.import_record_index].is_internal = true; } } @@ -6157,10 +6177,12 @@ pub fn NewParser( const name = p.loadNameFromRef(stmt.namespace_ref); stmt.namespace_ref = try p.declareSymbol(.import, star, name); if (comptime ParsePassSymbolUsageType != void) { - p.parse_pass_symbol_uses.put(name, .{ - .ref = stmt.namespace_ref, - .import_record_index = stmt.import_record_index, - }) catch unreachable; + if (!is_macro) { + p.parse_pass_symbol_uses.put(name, .{ + .ref = stmt.namespace_ref, + .import_record_index = stmt.import_record_index, + }) catch unreachable; + } } if (is_macro) { @@ -6190,10 +6212,12 @@ pub fn NewParser( try p.is_import_item.put(ref, true); name_loc.ref = ref; if (comptime ParsePassSymbolUsageType != void) { - p.parse_pass_symbol_uses.put(name, .{ - .ref = ref, - .import_record_index = stmt.import_record_index, - }) catch unreachable; + if (!is_macro) { + p.parse_pass_symbol_uses.put(name, .{ + .ref = ref, + .import_record_index = stmt.import_record_index, + }) catch unreachable; + } } if (is_macro) { @@ -6212,10 +6236,12 @@ pub fn NewParser( item_refs.putAssumeCapacity(item.alias, LocRef{ .loc = item.name.loc, .ref = ref }); if (comptime ParsePassSymbolUsageType != void) { - p.parse_pass_symbol_uses.put(name, .{ - .ref = ref, - .import_record_index = stmt.import_record_index, - }) catch unreachable; + if (!is_macro) { + p.parse_pass_symbol_uses.put(name, .{ + .ref = ref, + .import_record_index = stmt.import_record_index, + }) catch unreachable; + } } if (is_macro) { @@ -9752,9 +9778,12 @@ pub fn NewParser( }, .t_slash, .t_slash_equals => { try p.lexer.scanRegExp(); + // always set regex_flags_start to null to make sure we don't accidentally use the wrong value later + defer p.lexer.regex_flags_start = null; const value = p.lexer.raw(); try p.lexer.next(); - return p.e(E.RegExp{ .value = value }, loc); + + return p.e(E.RegExp{ .value = value, .flags_offset = p.lexer.regex_flags_start }, loc); }, .t_void => { try p.lexer.next(); @@ -10154,7 +10183,11 @@ pub fn NewParser( // do people do <API_URL>? fn jsxStringsToMemberExpression(p: *P, loc: logger.Loc, ref: Ref) Expr { p.recordUsage(ref); - return p.e(E.Identifier{ .ref = ref }, loc); + return p.e(E.Identifier{ + .ref = ref, + .can_be_removed_if_unused = true, + .call_can_be_unwrapped_if_unused = true, + }, loc); } // Note: The caller has already parsed the "import" keyword @@ -10733,7 +10766,8 @@ pub fn NewParser( }); } - fn visitExpr(p: *P, expr: Expr) Expr { + // public for JSNode.JSXWriter usage + pub fn visitExpr(p: *P, expr: Expr) Expr { if (only_scan_imports_and_do_not_visit) { @compileError("only_scan_imports_and_do_not_visit must not run this."); } @@ -10925,52 +10959,9 @@ pub fn NewParser( .e_jsx_element => |e_| { switch (comptime jsx_transform_type) { .macro => { - const IdentifierOrNodeType = union(Tag) { - identifier: Expr, - expression: Expr.Tag, - pub const Tag = enum { identifier, expression }; - }; - const tag: IdentifierOrNodeType = tagger: { - if (e_.tag) |_tag| { - switch (_tag.data) { - .e_string => |str| { - if (Expr.Tag.find(str.utf8)) |tagname| { - break :tagger IdentifierOrNodeType{ .expression = tagname }; - } - - p.log.addErrorFmt( - p.source, - expr.loc, - p.allocator, - "Invalid expression tag: \"<{s}>\". Valid tags are:\n" ++ Expr.Tag.valid_names_list ++ "\n", - .{str.utf8}, - ) catch unreachable; - break :tagger IdentifierOrNodeType{ .identifier = p.visitExpr(_tag) }; - }, - else => { - break :tagger IdentifierOrNodeType{ .identifier = p.visitExpr(_tag) }; - }, - } - } else { - break :tagger IdentifierOrNodeType{ .expression = Expr.Tag.e_array }; - } - }; - - for (e_.properties) |property, i| { - if (property.kind != .spread) { - e_.properties[i].key = p.visitExpr(e_.properties[i].key.?); - } - - if (property.value != null) { - e_.properties[i].value = p.visitExpr(e_.properties[i].value.?); - } - - if (property.initializer != null) { - e_.properties[i].initializer = p.visitExpr(e_.properties[i].initializer.?); - } - } - - return p.e(E.Missing{}, expr.loc); + const WriterType = js_ast.Macro.JSNode.NewJSXWriter(P); + var writer = WriterType.initWriter(p, &BunJSX.bun_jsx_identifier); + return writer.writeFunctionCall(e_.*); }, .react => { const tag: Expr = tagger: { @@ -11056,6 +11047,7 @@ pub fn NewParser( // Either: // jsxDEV(type, arguments, key, isStaticChildren, source, self) // jsx(type, arguments, key) + const include_filename = FeatureFlags.include_filename_in_jsx and p.options.jsx.development; const args = p.allocator.alloc(Expr, if (p.options.jsx.development) @as(usize, 6) else @as(usize, 4)) catch unreachable; args[0] = tag; var props = List(G.Property).fromOwnedSlice(p.allocator, e_.properties); @@ -11134,27 +11126,34 @@ pub fn NewParser( }, }; - var source = p.allocator.alloc(G.Property, 2) catch unreachable; - p.recordUsage(p.jsx_filename.ref); - source[0] = G.Property{ - .key = Expr{ .loc = expr.loc, .data = Prefill.Data.Filename }, - .value = p.e(E.Identifier{ .ref = p.jsx_filename.ref }, expr.loc), - }; + if (include_filename) { + var source = p.allocator.alloc(G.Property, 2) catch unreachable; + p.recordUsage(p.jsx_filename.ref); + source[0] = G.Property{ + .key = Expr{ .loc = expr.loc, .data = Prefill.Data.Filename }, + .value = p.e(E.Identifier{ + .ref = p.jsx_filename.ref, + .can_be_removed_if_unused = true, + }, expr.loc), + }; - source[1] = G.Property{ - .key = Expr{ .loc = expr.loc, .data = Prefill.Data.LineNumber }, - .value = p.e(E.Number{ .value = @intToFloat(f64, expr.loc.start) }, expr.loc), - }; + source[1] = G.Property{ + .key = Expr{ .loc = expr.loc, .data = Prefill.Data.LineNumber }, + .value = p.e(E.Number{ .value = @intToFloat(f64, expr.loc.start) }, expr.loc), + }; - // Officially, they ask for columnNumber. But I don't see any usages of it in the code! - // source[2] = G.Property{ - // .key = Expr{ .loc = expr.loc, .data = Prefill.Data.ColumnNumber }, - // .value = p.e(E.Number{ .value = @intToFloat(f64, expr.loc.start) }, expr.loc), - // }; + // Officially, they ask for columnNumber. But I don't see any usages of it in the code! + // source[2] = G.Property{ + // .key = Expr{ .loc = expr.loc, .data = Prefill.Data.ColumnNumber }, + // .value = p.e(E.Number{ .value = @intToFloat(f64, expr.loc.start) }, expr.loc), + // }; + args[4] = p.e(E.Object{ + .properties = source, + }, expr.loc); + } else { + args[4] = p.e(E.Object{}, expr.loc); + } - args[4] = p.e(E.Object{ - .properties = source, - }, expr.loc); args[5] = Expr{ .data = Prefill.Data.This, .loc = expr.loc }; } @@ -11231,6 +11230,89 @@ pub fn NewParser( const is_stmt_expr = @as(Expr.Tag, p.stmt_expr_value) == .e_binary and expr.data.e_binary == p.stmt_expr_value.e_binary; const was_anonymous_named_expr = p.isAnonymousNamedExpr(e_.right); + if (comptime jsx_transform_type == .macro) { + if (e_.op == Op.Code.bin_instanceof and (e_.right.data == .e_jsx_element or e_.left.data == .e_jsx_element)) { + // foo instanceof <string /> + // -> + // bunJSX.isNodeType(foo, 13) + + // <string /> instanceof foo + // -> + // bunJSX.isNodeType(foo, 13) + var call_args = p.allocator.alloc(Expr, 2) catch unreachable; + call_args[0] = e_.left; + call_args[1] = e_.right; + + if (e_.right.data == .e_jsx_element) { + const jsx_element = e_.right.data.e_jsx_element; + if (jsx_element.tag) |tag| { + if (tag.data == .e_string) { + const tag_string = tag.data.e_string.utf8; + if (js_ast.Macro.JSNode.Tag.names.get(tag_string)) |node_tag| { + call_args[1] = Expr{ .loc = tag.loc, .data = js_ast.Macro.JSNode.Tag.ids.get(node_tag) }; + } else { + p.log.addRangeErrorFmt( + p.source, + js_lexer.rangeOfIdentifier(p.source, tag.loc), + p.allocator, + "Invalid JSX tag: \"{s}\"", + .{tag_string}, + ) catch unreachable; + return expr; + } + } + } else { + call_args[1] = p.visitExpr(call_args[1]); + } + } else { + call_args[1] = p.visitExpr(call_args[1]); + } + + if (e_.left.data == .e_jsx_element) { + const jsx_element = e_.left.data.e_jsx_element; + if (jsx_element.tag) |tag| { + if (tag.data == .e_string) { + const tag_string = tag.data.e_string.utf8; + if (js_ast.Macro.JSNode.Tag.names.get(tag_string)) |node_tag| { + call_args[0] = Expr{ .loc = tag.loc, .data = js_ast.Macro.JSNode.Tag.ids.get(node_tag) }; + } else { + p.log.addRangeErrorFmt( + p.source, + js_lexer.rangeOfIdentifier(p.source, tag.loc), + p.allocator, + "Invalid JSX tag: \"{s}\"", + .{tag_string}, + ) catch unreachable; + return expr; + } + } + } else { + call_args[0] = p.visitExpr(call_args[0]); + } + } else { + call_args[0] = p.visitExpr(call_args[0]); + } + + return p.e( + E.Call{ + .target = p.e( + E.Dot{ + .name = "isNodeType", + .name_loc = expr.loc, + .target = p.e(BunJSX.bun_jsx_identifier, expr.loc), + .can_be_removed_if_unused = true, + .call_can_be_unwrapped_if_unused = true, + }, + expr.loc, + ), + .args = call_args, + .can_be_unwrapped_if_unused = true, + }, + expr.loc, + ); + } + } + e_.left = p.visitExprInOut(e_.left, ExprIn{ .assign_target = e_.op.binaryAssignTarget(), }); @@ -11857,7 +11939,10 @@ pub fn NewParser( var has_spread = false; var has_proto = false; - for (e_.properties) |*property, i| { + var i: usize = 0; + while (i < e_.properties.len) : (i += 1) { + var property = &e_.properties[i]; + if (property.kind != .spread) { property.key = p.visitExpr(property.key orelse Global.panic("Expected property key", .{})); const key = property.key.?; @@ -11949,11 +12034,33 @@ pub fn NewParser( .has_chain_parent = (e_.optional_chain orelse js_ast.OptionalChain.start) == .ccontinue, }); - // TODO: wan about import namespace call - var has_spread = false; - for (e_.args) |*arg| { - arg.* = p.visitExpr(arg.*); - has_spread = has_spread or @as(Expr.Tag, arg.data) == .e_spread; + // Copy the call side effect flag over if this is a known target + switch (e_.target.data) { + .e_identifier => |ident| { + e_.can_be_unwrapped_if_unused = e_.can_be_unwrapped_if_unused or ident.call_can_be_unwrapped_if_unused; + }, + .e_dot => |dot| { + e_.can_be_unwrapped_if_unused = e_.can_be_unwrapped_if_unused or dot.call_can_be_unwrapped_if_unused; + }, + else => {}, + } + + const is_macro_ref: bool = if (comptime FeatureFlags.is_macro_enabled and + jsx_transform_type != .macro) + e_.target.data == .e_import_identifier and p.macro_refs.contains(e_.target.data.e_import_identifier.ref) + else + false; + + { + const old_ce = p.options.ignore_dce_annotations; + defer p.options.ignore_dce_annotations = old_ce; + if (is_macro_ref) + p.options.ignore_dce_annotations = true; + + for (e_.args) |_, i| { + const arg = e_.args[i]; + e_.args[i] = p.visitExpr(arg); + } } if (e_.optional_chain == null and @as(Expr.Tag, e_.target.data) == .e_identifier and e_.target.data.e_identifier.ref.eql(p.require_ref)) { @@ -11969,22 +12076,22 @@ pub fn NewParser( } if (comptime FeatureFlags.is_macro_enabled and jsx_transform_type != .macro) { - if (e_.target.data == .e_import_identifier) { + if (is_macro_ref) { const ref = e_.target.data.e_import_identifier.ref; - if (p.macro_refs.get(ref)) |import_record_id| { - const name = p.symbols.items[ref.inner_index].original_name; - const record = &p.import_records.items[import_record_id]; - return p.options.macro_context.call( - record.path.text, - p.source.path.sourceDir(), - p.log, - p.source, - record.range, - expr, - &.{}, - name, - ) catch return expr; - } + const import_record_id = p.macro_refs.get(ref).?; + const name = p.symbols.items[ref.inner_index].original_name; + const record = &p.import_records.items[import_record_id]; + const copied = Expr{ .loc = expr.loc, .data = .{ .e_call = e_ } }; + return p.options.macro_context.call( + record.path.text, + p.source.path.sourceDir(), + p.log, + p.source, + record.range, + copied, + &.{}, + name, + ) catch return expr; } } @@ -12311,6 +12418,7 @@ pub fn NewParser( .un_typeof, .un_void, .un_not => { return p.exprCanBeRemovedIfUnused(&ex.value); }, + else => {}, } }, @@ -12571,7 +12679,7 @@ pub fn NewParser( data.value.expr = p.visitExpr(expr); // // Optionally preserve the name - data.value.expr = p.maybeKeepExprSymbolName(expr, "default", was_anonymous_named_expr); + data.value.expr = p.maybeKeepExprSymbolName(data.value.expr, "default", was_anonymous_named_expr); // Discard type-only export default statements if (is_typescript_enabled) { @@ -12594,7 +12702,7 @@ pub fn NewParser( if (p.options.enable_bundling) { var export_default_args = p.allocator.alloc(Expr, 2) catch unreachable; export_default_args[0] = p.e(E.Identifier{ .ref = p.exports_ref }, expr.loc); - export_default_args[1] = expr; + export_default_args[1] = data.value.expr; stmts.append(p.s(S.SExpr{ .value = p.callRuntime(expr.loc, "__exportDefault", export_default_args) }, expr.loc)) catch unreachable; return; } @@ -15250,7 +15358,7 @@ const JSParserMacro = NewParser(.{ .jsx = .macro, }); const TSParserMacro = NewParser(.{ - .jsx = .react, + .jsx = .macro, .typescript = true, }); diff --git a/src/js_printer.zig b/src/js_printer.zig index 3bdeb09ba..1419dbef2 100644 --- a/src/js_printer.zig +++ b/src/js_printer.zig @@ -567,7 +567,7 @@ pub fn NewPrinter( std.fmt.formatFloatScientific(float, .{}, p) catch unreachable; } - pub fn printQuotedUTF16(e: *Printer, text: JavascriptString, quote: u8) void { + pub fn printQuotedUTF16(e: *Printer, text: []const u16, quote: u8) void { // utf-8 is a max of 4 bytes // we leave two extra chars for "\" and "u" var temp = [6]u8{ 0, 0, 0, 0, 0, 0 }; @@ -857,6 +857,9 @@ pub fn NewPrinter( } } + // noop for now + pub inline fn printPure(p: *Printer) void {} + pub fn printQuotedUTF8(p: *Printer, str: string, allow_backtick: bool) void { const quote = p.bestQuoteCharForString(str, allow_backtick); p.print(quote); @@ -872,7 +875,7 @@ pub fn NewPrinter( } } - pub inline fn canPrintIdentifierUTF16(p: *Printer, name: JavascriptString) bool { + pub inline fn canPrintIdentifierUTF16(p: *Printer, name: []const u16) bool { // TODO: fix this // this is commented out because something isn't quite right // the problem may lie in isIdentifierUTF16, or it may lie in how these are allocated. @@ -930,7 +933,7 @@ pub fn NewPrinter( } if (has_pure_comment) { - p.print("/* @__PURE__ */ "); + p.printPure(); } p.printSpaceBeforeIdentifier(); @@ -978,7 +981,7 @@ pub fn NewPrinter( if (has_pure_comment) { const was_stmt_start = p.stmt_start == p.writer.written; - p.print("/* @__PURE__ */ "); + p.printPure(); if (was_stmt_start) { p.stmt_start = p.writer.written; } @@ -3707,7 +3710,7 @@ pub fn NewPrinter( p.print(identifier); } - pub fn printIdentifierUTF16(p: *Printer, name: JavascriptString) !void { + pub fn printIdentifierUTF16(p: *Printer, name: []const u16) !void { var temp = [_]u8{ 0, 0, 0, 0, 0, 0 }; const n = name.len; var i: usize = 0; diff --git a/src/runtime.zig b/src/runtime.zig index c8aed0dc7..e0dd3bab7 100644 --- a/src/runtime.zig +++ b/src/runtime.zig @@ -252,6 +252,7 @@ pub const Runtime = struct { "__exportDefault", }; pub const Name = "<RUNTIME"; + pub const alt_name = "__runtime.js"; pub const Iterator = struct { i: usize = 0, diff --git a/src/string_immutable.zig b/src/string_immutable.zig index 1a144a747..c9437b757 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -437,11 +437,11 @@ pub fn index(self: string, str: string) i32 { } } -pub fn eqlUtf16(comptime self: string, other: JavascriptString) bool { +pub fn eqlUtf16(comptime self: string, other: []const u16) bool { return std.mem.eql(u16, std.unicode.utf8ToUtf16LeStringLiteral(self), other); } -pub fn toUTF8Alloc(allocator: *std.mem.Allocator, js: JavascriptString) !string { +pub fn toUTF8Alloc(allocator: *std.mem.Allocator, js: []const u16) !string { var temp: [4]u8 = undefined; var list = std.ArrayList(u8).initCapacity(allocator, js.len) catch unreachable; var i: usize = 0; @@ -461,7 +461,7 @@ pub fn toUTF8Alloc(allocator: *std.mem.Allocator, js: JavascriptString) !string } // Check utf16 string equals utf8 string without allocating extra memory -pub fn utf16EqlString(text: []u16, str: string) bool { +pub fn utf16EqlString(text: []const u16, str: string) bool { if (text.len > str.len) { // Strings can't be equal if UTF-16 encoding is longer than UTF-8 encoding return false; @@ -603,7 +603,7 @@ pub fn trim(slice: anytype, values_to_strip: []const u8) @TypeOf(slice) { return slice[begin..end]; } -pub fn containsNonBmpCodePointUTF16(_text: JavascriptString) bool { +pub fn containsNonBmpCodePointUTF16(_text: []const u16) bool { const n = _text.len; if (n > 0) { var i: usize = 0; |