aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-05-16 23:25:12 -0700
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-05-16 23:25:12 -0700
commit154e049638753abc10ed0eca2012685fe3b831be (patch)
treebdeb6b0bf8137ee36df0aab436ac50713ddeb5ef
parente80f865974df7aae5e2f6abb966b36497da693c6 (diff)
downloadbun-154e049638753abc10ed0eca2012685fe3b831be.tar.gz
bun-154e049638753abc10ed0eca2012685fe3b831be.tar.zst
bun-154e049638753abc10ed0eca2012685fe3b831be.zip
lots
Former-commit-id: 9ccb4dd082afbc4f94982bf092360487232d8b60
-rw-r--r--.vscode/launch.json58
-rw-r--r--src/allocators.zig304
-rw-r--r--src/ast/base.zig2
-rw-r--r--src/bundler.zig1
-rw-r--r--src/cache.zig31
-rw-r--r--src/fs.zig157
-rw-r--r--src/js_lexer.zig9
-rw-r--r--src/js_parser/js_parser.zig220
-rw-r--r--src/js_printer.zig29
-rw-r--r--src/json_parser.zig12
-rw-r--r--src/resolver/package_json.zig7
-rw-r--r--src/resolver/resolve_path.zig521
-rw-r--r--src/resolver/resolver.zig401
-rw-r--r--src/test/fixtures/exports-bug.js2342
-rw-r--r--src/test/fixtures/label-continue-break-bug.js125
-rw-r--r--src/test/fixtures/symbols-bug.js16
-rw-r--r--src/test/tester.zig4
17 files changed, 3739 insertions, 500 deletions
diff --git a/.vscode/launch.json b/.vscode/launch.json
index ac77daf98..ac18a6ebb 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -21,14 +21,30 @@
// "--resolve=disable",
// "--cwd",
// "${workspaceFolder}",
- // "src/test/fixtures/cannot-assign-to-import-bug.js",
+ // "/Users/jarredsumner/Code/esdev/src/test/fixtures/exports-bug.js",
+ // "-o",
+ // "out"
+ // ],
+ // "cwd": "${workspaceFolder}",
+ // "console": "internalConsole"
+ // }
+ // {
+ // "type": "lldb",
+ // "request": "launch",
+ // "name": "Dev Launch",
+ // "program": "${workspaceFolder}/build/debug/macos-x86_64/esdev",
+ // "preLaunchTask": "build",
+ // "args": [
+ // "--resolve=disable",
+ // "--cwd",
+ // "/Users/jarredsumner/Code/esdev/src/test/fixtures/",
+ // "/Users/jarredsumner/Code/esdev/src/test/fixtures/symbols-bug.js",
// "-o",
// "out"
// ],
// "cwd": "${workspaceFolder}",
// "console": "internalConsole"
// }
-
{
"type": "lldb",
"request": "launch",
@@ -38,8 +54,8 @@
"args": [
"--resolve=dev",
"--cwd",
- "./src/api/demo",
- "pages/index.js",
+ "/Users/jarredsumner/Builds/esbuild/bench/three/src/",
+ "./entry.js",
"-o",
"out"
],
@@ -50,6 +66,40 @@
// "type": "lldb",
// "request": "launch",
// "name": "Dev Launch",
+ // "program": "${workspaceFolder}/build/debug/macos-x86_64/esdev",
+ // "preLaunchTask": "build",
+ // "args": [
+ // "--resolve=dev",
+ // "--cwd",
+ // "/Users/jarredsumner/Builds/esbuild/bench/three/src/",
+ // "./entry.js",
+ // "-o",
+ // "out"
+ // ],
+ // "cwd": "${workspaceFolder}",
+ // "console": "internalConsole"
+ // }
+ // {
+ // "type": "lldb",
+ // "request": "launch",
+ // "name": "Dev Launch",
+ // "program": "${workspaceFolder}/build/debug/macos-x86_64/esdev",
+ // "preLaunchTask": "build",
+ // "args": [
+ // "--resolve=dev",
+ // "--cwd",
+ // "./src/api/demo",
+ // "pages/index.js",
+ // "-o",
+ // "out"
+ // ],
+ // "cwd": "${workspaceFolder}",
+ // "console": "internalConsole"
+ // }
+ // {
+ // "type": "lldb",
+ // "request": "launch",
+ // "name": "Dev Launch",
// "program": "${workspaceFolder}/build/bin/debug/esdev",
// "preLaunchTask": "build",
// "args": [
diff --git a/src/allocators.zig b/src/allocators.zig
new file mode 100644
index 000000000..b6a13ba47
--- /dev/null
+++ b/src/allocators.zig
@@ -0,0 +1,304 @@
+const std = @import("std");
+
+const Wyhash = std.hash.Wyhash;
+const FixedBufferAllocator = std.heap.FixedBufferAllocator;
+
+// https://en.wikipedia.org/wiki/.bss#BSS_in_C
+pub fn BSSSectionAllocator(comptime size: usize) type {
+ return struct {
+ var backing_buf: [size]u8 = undefined;
+ var fixed_buffer_allocator = FixedBufferAllocator.init(&backing_buf);
+ var buf_allocator = &fixed_buffer_allocator.allocator;
+ const Allocator = std.mem.Allocator;
+ const Self = @This();
+
+ allocator: Allocator,
+ fallback_allocator: *Allocator,
+
+ is_overflowed: bool = false,
+
+ pub fn get(self: *Self) *Allocator {
+ return &self.allocator;
+ }
+
+ pub fn init(fallback_allocator: *Allocator) Self {
+ return Self{ .fallback_allocator = fallback_allocator, .allocator = Allocator{
+ .allocFn = BSSSectionAllocator(size).alloc,
+ .resizeFn = BSSSectionAllocator(size).resize,
+ } };
+ }
+
+ pub fn alloc(
+ allocator: *Allocator,
+ len: usize,
+ ptr_align: u29,
+ len_align: u29,
+ return_address: usize,
+ ) error{OutOfMemory}![]u8 {
+ const self = @fieldParentPtr(Self, "allocator", allocator);
+ return buf_allocator.allocFn(buf_allocator, len, ptr_align, len_align, return_address) catch |err| {
+ self.is_overflowed = true;
+ return self.fallback_allocator.allocFn(self.fallback_allocator, len, ptr_align, len_align, return_address);
+ };
+ }
+
+ pub fn resize(
+ allocator: *Allocator,
+ buf: []u8,
+ buf_align: u29,
+ new_len: usize,
+ len_align: u29,
+ return_address: usize,
+ ) error{OutOfMemory}!usize {
+ const self = @fieldParentPtr(Self, "allocator", allocator);
+ if (fixed_buffer_allocator.ownsPtr(buf.ptr)) {
+ return fixed_buffer_allocator.allocator.resizeFn(&fixed_buffer_allocator.allocator, buf, buf_align, new_len, len_align, return_address);
+ } else {
+ return self.fallback_allocator.resizeFn(self.fallback_allocator, buf, buf_align, new_len, len_align, return_address);
+ }
+ }
+ };
+}
+
+const HashKeyType = u64;
+const IndexMap = std.HashMapUnmanaged(HashKeyType, u32, hash_hashFn, hash_eqlFn, 80);
+pub const Result = struct {
+ hash: HashKeyType,
+ index: u32,
+ status: ItemStatus,
+
+ pub fn hasCheckedIfExists(r: *Result) bool {
+ return r.status != .unknown;
+ }
+};
+const Seed = 999;
+pub const NotFound = std.math.maxInt(u32);
+pub const Unassigned = NotFound - 1;
+
+pub fn hash_hashFn(key: HashKeyType) HashKeyType {
+ return key;
+}
+
+pub fn hash_eqlFn(a: HashKeyType, b: HashKeyType) bool {
+ return a == b;
+}
+
+pub const ItemStatus = packed enum(u3) {
+ unknown,
+ exists,
+ not_found,
+};
+
+const hasDeinit = std.meta.trait.hasFn("deinit")(ValueType);
+
+pub fn BSSMap(comptime ValueType: type, comptime count: anytype, store_keys: bool, estimated_key_length: usize) type {
+ const max_index = count - 1;
+ const BSSMapType = struct {
+ pub var backing_buf: [count]ValueType = undefined;
+ pub var backing_buf_used: u16 = 0;
+ const Allocator = std.mem.Allocator;
+ const Self = @This();
+
+ // const HashTableAllocator = BSSSectionAllocator(@bitSizeOf(HashKeyType) * count * 2);
+
+ index: IndexMap,
+ overflow_list: std.ArrayListUnmanaged(ValueType),
+ allocator: *Allocator,
+
+ pub var instance: Self = undefined;
+
+ pub fn init(allocator: *std.mem.Allocator) *Self {
+ instance = Self{
+ .index = IndexMap{},
+ .allocator = allocator,
+ .overflow_list = std.ArrayListUnmanaged(ValueType){},
+ };
+
+ return &instance;
+ }
+
+ pub fn isOverflowing() bool {
+ return backing_buf_used >= @as(u16, count);
+ }
+
+ pub fn getOrPut(self: *Self, key: []const u8) !Result {
+ const _key = Wyhash.hash(Seed, key);
+ var index = try self.index.getOrPut(self.allocator, _key);
+
+ if (index.found_existing) {
+ return Result{
+ .hash = _key,
+ .index = index.entry.value,
+ .status = switch (index.entry.value) {
+ NotFound => .not_found,
+ Unassigned => .unknown,
+ else => .exists,
+ },
+ };
+ }
+ index.entry.value = Unassigned;
+
+ return Result{
+ .hash = _key,
+ .index = Unassigned,
+ .status = .unknown,
+ };
+ }
+
+ pub fn get(self: *const Self, key: []const u8) ?*ValueType {
+ const _key = Wyhash.hash(Seed, key);
+ const index = self.index.get(_key) orelse return null;
+ return self.atIndex(index);
+ }
+
+ pub fn markNotFound(self: *Self, result: Result) void {
+ self.index.put(self.allocator, result.hash, NotFound) catch unreachable;
+ }
+
+ pub fn atIndex(self: *const Self, index: u32) ?*ValueType {
+ return switch (index) {
+ NotFound, Unassigned => null,
+ 0...max_index => &backing_buf[index],
+ else => &self.overflow_list.items[index - count],
+ };
+ }
+
+ pub fn put(self: *Self, result: *Result, value: ValueType) !*ValueType {
+ var index: u32 = @intCast(u32, backing_buf_used + 1);
+ if (index >= max_index) {
+ const real_index = self.overflow_list.items.len;
+ index += @truncate(u32, real_index);
+ try self.overflow_list.append(self.allocator, value);
+ result.index = index;
+ self.index.putAssumeCapacity(result.hash, index);
+ return &self.overflow_list.items[real_index];
+ } else {
+ backing_buf_used += 1;
+ backing_buf[index] = value;
+ result.index = index;
+ self.index.putAssumeCapacity(result.hash, index);
+ if (backing_buf_used >= max_index - 1) {
+ self.overflow_list = try @TypeOf(self.overflow_list).initCapacity(self.allocator, count);
+ }
+ return &backing_buf[index];
+ }
+ }
+
+ pub fn remove(self: *Self, key: string) u32 {
+ const _key = Wyhash.hash(Seed, key);
+ const index = self.index.get(_key) orelse return;
+ switch (index) {
+ Unassigned => {
+ self.index.remove(_key);
+ },
+ NotFound => {
+ self.index.remove(_key);
+ },
+ 0...max_index => {
+ if (hasDeinit(ValueType)) {
+ backing_buf[index].deinit();
+ }
+ backing_buf[index] = undefined;
+ },
+ else => {
+ const i = index - count;
+ if (hasDeinit(ValueType)) {
+ self.overflow_list.items[i].deinit();
+ }
+ self.overflow_list.items[index - count] = undefined;
+ },
+ }
+
+ return index;
+ }
+ };
+ if (!store_keys) {
+ return BSSMapType;
+ }
+
+ return struct {
+ map: *BSSMapType,
+ const Self = @This();
+ pub var instance: Self = undefined;
+ var key_list_buffer: [count * estimated_key_length]u8 = undefined;
+ var key_list_buffer_used: usize = 0;
+ var key_list_slices: [count][]u8 = undefined;
+ var key_list_overflow: std.ArrayListUnmanaged([]u8) = undefined;
+
+ pub fn init(allocator: *std.mem.Allocator) *Self {
+ instance = Self{
+ .map = BSSMapType.init(allocator),
+ };
+
+ return &instance;
+ }
+
+ pub fn isOverflowing() bool {
+ return instance.map.backing_buf_used >= count;
+ }
+ pub fn getOrPut(self: *Self, key: []const u8) !Result {
+ return try self.map.getOrPut(key);
+ }
+ pub fn get(self: *Self, key: []const u8) ?*ValueType {
+ return @call(.{ .modifier = .always_inline }, BSSMapType.get, .{ self.map, key });
+ }
+
+ pub fn atIndex(self: *Self, index: u32) ?*ValueType {
+ return @call(.{ .modifier = .always_inline }, BSSMapType.atIndex, .{ self.map, index });
+ }
+
+ pub fn keyAtIndex(self: *Self, index: u32) ?[]const u8 {
+ return switch (index) {
+ Unassigned, NotFound => null,
+ 0...max_index => {
+ return key_list_slices[index];
+ },
+ else => {
+ return key_list_overflow.items[index - count];
+ },
+ };
+ }
+
+ pub fn put(self: *Self, key: anytype, comptime store_key: bool, result: *Result, value: ValueType) !*ValueType {
+ var ptr = try self.map.put(result, value);
+ if (store_key) {
+ try self.putKey(key, result);
+ }
+
+ return ptr;
+ }
+
+ pub fn putKey(self: *Self, key: anytype, result: *Result) !void {
+ if (key_list_buffer_used + key.len < key_list_buffer.len) {
+ const start = key_list_buffer_used;
+ key_list_buffer_used += key.len;
+ var slice = key_list_buffer[start..key_list_buffer_used];
+ std.mem.copy(u8, slice, key);
+
+ if (result.index < count) {
+ key_list_slices[result.index] = slice;
+ } else {
+ try key_list_overflow.append(self.map.allocator, slice);
+ }
+ } else if (result.index > key_list_overflow.items.len) {
+ try key_list_overflow.append(self.map.allocator, try self.map.allocator.dupe(u8, key));
+ } else {
+ const real_index = result.index - count;
+ if (key_list_overflow.items[real_index].len > 0) {
+ self.map.allocator.free(key_list_overflow.items[real_index]);
+ }
+
+ key_list_overflow.items[real_index] = try self.map.allocator.dupe(u8, key);
+ }
+ }
+
+ pub fn markNotFound(self: *Self, result: Result) void {
+ self.map.markNotFound(result);
+ }
+
+ // For now, don't free the keys.
+ pub fn remove(self: *Self, key: string) u32 {
+ return self.map.remove(key);
+ }
+ };
+}
diff --git a/src/ast/base.zig b/src/ast/base.zig
index 904ad97bd..8e2635004 100644
--- a/src/ast/base.zig
+++ b/src/ast/base.zig
@@ -34,7 +34,7 @@ pub const Ref = packed struct {
.source_index = std.math.maxInt(Ref.Int),
};
pub fn toInt(int: anytype) Int {
- return std.math.cast(Ref.Int, int) catch 0;
+ return std.math.lossyCast(Ref.Int, int);
}
pub fn isNull(self: *const Ref) bool {
return self.source_index == std.math.maxInt(Ref.Int) and self.inner_index == std.math.maxInt(Ref.Int);
diff --git a/src/bundler.zig b/src/bundler.zig
index 34983a9b8..a59120840 100644
--- a/src/bundler.zig
+++ b/src/bundler.zig
@@ -265,7 +265,6 @@ pub const Bundler = struct {
try msg.writeFormat(std.io.getStdOut().writer());
}
}
-
switch (bundler.options.resolve_mode) {
.lazy, .dev, .bundle => {
while (bundler.resolve_queue.readItem()) |item| {
diff --git a/src/cache.zig b/src/cache.zig
index 88fa6e9ea..22953a3a2 100644
--- a/src/cache.zig
+++ b/src/cache.zig
@@ -19,7 +19,7 @@ pub const Cache = struct {
pub fn init(allocator: *std.mem.Allocator) Set {
return Set{
- .js = JavaScript{},
+ .js = JavaScript.init(allocator),
.fs = Fs{
.mutex = Mutex.init(),
.entries = std.StringHashMap(Fs.Entry).init(allocator),
@@ -130,13 +130,14 @@ pub const Cache = struct {
};
pub const JavaScript = struct {
- pub const Entry = struct {
- ast: js_ast.Ast,
- source: logger.Source,
- ok: bool,
- msgs: []logger.Msg,
- };
+ mutex: Mutex,
+ entries: std.StringHashMap(Result),
+
pub const Result = js_ast.Result;
+
+ pub fn init(allocator: *std.mem.Allocator) JavaScript {
+ return JavaScript{ .mutex = Mutex.init(), .entries = std.StringHashMap(Result).init(allocator) };
+ }
// For now, we're not going to cache JavaScript ASTs.
// It's probably only relevant when bundling for production.
pub fn parse(
@@ -147,19 +148,31 @@ pub const Cache = struct {
log: *logger.Log,
source: *const logger.Source,
) anyerror!?js_ast.Ast {
+ cache.mutex.lock();
+ defer cache.mutex.unlock();
+
+ var get_or_put_result = try cache.entries.getOrPut(source.key_path.text);
+
+ if (get_or_put_result.found_existing) {
+ return if (get_or_put_result.entry.value.ok) get_or_put_result.entry.value.ast else null;
+ }
+
var temp_log = logger.Log.init(allocator);
var parser = js_parser.Parser.init(opts, &temp_log, source, defines, allocator) catch |err| {
temp_log.appendTo(log) catch {};
+ get_or_put_result.entry.value = Result{ .ast = undefined, .ok = false };
+
return null;
};
- const result = parser.parse() catch |err| {
+ get_or_put_result.entry.value = parser.parse() catch |err| {
+ get_or_put_result.entry.value = Result{ .ast = undefined, .ok = false };
temp_log.appendTo(log) catch {};
return null;
};
temp_log.appendTo(log) catch {};
- return if (result.ok) result.ast else null;
+ return if (get_or_put_result.entry.value.ok) get_or_put_result.entry.value.ast else null;
}
};
diff --git a/src/fs.zig b/src/fs.zig
index 86a6550c4..99998d446 100644
--- a/src/fs.zig
+++ b/src/fs.zig
@@ -6,11 +6,13 @@ const expect = std.testing.expect;
const Mutex = sync.Mutex;
const Semaphore = sync.Semaphore;
-const resolvePath = @import("./resolver/resolve_path.zig").resolvePath;
+const path_handler = @import("./resolver/resolve_path.zig");
+
+const allocators = @import("./allocators.zig");
// pub const FilesystemImplementation = @import("fs_impl.zig");
-threadlocal var scratch_lookup_buffer = [_]u8{0} ** 255;
+threadlocal var scratch_lookup_buffer: [256]u8 = undefined;
pub const FileSystem = struct {
allocator: *std.mem.Allocator,
@@ -41,6 +43,14 @@ pub const FileSystem = struct {
dir: string,
data: EntryMap,
+ pub fn updateDir(i: *DirEntry, dir: string) void {
+ var iter = i.data.iterator();
+ i.dir = dir;
+ while (iter.next()) |entry| {
+ entry.value.dir = dir;
+ }
+ }
+
pub fn empty(dir: string, allocator: *std.mem.Allocator) DirEntry {
return DirEntry{ .dir = dir, .data = EntryMap.init(allocator) };
}
@@ -154,10 +164,26 @@ pub const FileSystem = struct {
// pub fn readDir(fs: *FileSystemEntry, path: string) ?[]string {
// }
+ pub fn normalize(f: *@This(), str: string) string {
+ return @call(.{ .modifier = .always_inline }, path_handler.normalizeAndJoin, .{ f.top_level_dir, .auto, str });
+ }
+
+ pub fn join(f: *@This(), parts: anytype) string {
+ return @call(.{ .modifier = .always_inline }, path_handler.normalizeAndJoinString, .{
+ f.top_level_dir,
+ parts,
+ .auto,
+ });
+ }
+
+ pub fn joinAlloc(f: *@This(), allocator: *std.mem.Allocator, parts: anytype) !string {
+ const joined = f.join(parts);
+ return try allocator.dupe(u8, joined);
+ }
pub const RealFS = struct {
entries_mutex: Mutex = Mutex.init(),
- entries: std.StringHashMap(EntriesOption),
+ entries: *EntriesOption.Map,
allocator: *std.mem.Allocator,
do_not_cache_entries: bool = false,
limiter: Limiter,
@@ -166,7 +192,7 @@ pub const FileSystem = struct {
pub fn init(allocator: *std.mem.Allocator, enable_watcher: bool) RealFS {
return RealFS{
- .entries = std.StringHashMap(EntriesOption).init(allocator),
+ .entries = EntriesOption.Map.init(allocator),
.allocator = allocator,
.limiter = Limiter.init(allocator),
.watcher = if (enable_watcher) std.StringHashMap(WatchData).init(allocator) else null,
@@ -276,6 +302,11 @@ pub const FileSystem = struct {
entries,
err,
};
+
+ // This custom map implementation:
+ // - Preallocates a fixed amount of directory name space
+ // - Doesn't store directory names which don't exist.
+ pub const Map = allocators.BSSMap(EntriesOption, 1024, true, 128);
};
// Limit the number of files open simultaneously to avoid ulimit issues
@@ -305,15 +336,20 @@ pub const FileSystem = struct {
}
};
- fn readdir(fs: *RealFS, _dir: string) !DirEntry {
+ pub fn openDir(fs: *RealFS, unsafe_dir_string: string) std.fs.File.OpenError!std.fs.Dir {
+ return try std.fs.openDirAbsolute(unsafe_dir_string, std.fs.Dir.OpenDirOptions{ .iterate = true, .access_sub_paths = true });
+ }
+
+ fn readdir(
+ fs: *RealFS,
+ _dir: string,
+ handle: std.fs.Dir,
+ ) !DirEntry {
fs.limiter.before();
defer fs.limiter.after();
- var handle = try std.fs.openDirAbsolute(_dir, std.fs.Dir.OpenDirOptions{ .iterate = true, .access_sub_paths = true });
- defer handle.close();
-
var iter: std.fs.Dir.Iterator = handle.iterate();
- var dir = DirEntry{ .data = DirEntry.EntryMap.init(fs.allocator), .dir = _dir };
+ var dir = DirEntry.init("", fs.allocator);
errdefer dir.deinit();
while (try iter.next()) |_entry| {
const entry: std.fs.Dir.Entry = _entry;
@@ -342,7 +378,7 @@ pub const FileSystem = struct {
var entry_ptr = try fs.allocator.create(Entry);
entry_ptr.* = Entry{
.base = name,
- .dir = _dir,
+ .dir = "",
.mutex = Mutex.init(),
// Call "stat" lazily for performance. The "@material-ui/icons" package
// contains a directory with over 11,000 entries in it and running "stat"
@@ -356,12 +392,11 @@ pub const FileSystem = struct {
try dir.data.put(name, entry_ptr);
}
- // Copy at the bottom here so in the event of an error, we don't deinit the dir string.
- dir.dir = _dir;
+
return dir;
}
- fn readDirectoryError(fs: *RealFS, dir: string, err: anyerror) !void {
+ fn readDirectoryError(fs: *RealFS, dir: string, err: anyerror) !*EntriesOption {
if (fs.watcher) |*watcher| {
fs.watcher_mutex.lock();
defer fs.watcher_mutex.unlock();
@@ -371,27 +406,52 @@ pub const FileSystem = struct {
if (!fs.do_not_cache_entries) {
fs.entries_mutex.lock();
defer fs.entries_mutex.unlock();
-
- try fs.entries.put(dir, EntriesOption{
+ var get_or_put_result = try fs.entries.getOrPut(dir);
+ var opt = try fs.entries.put(null, false, &get_or_put_result, EntriesOption{
.err = DirEntry.Err{ .original_err = err, .canonical_error = err },
});
+
+ return opt;
}
+
+ temp_entries_option = EntriesOption{
+ .err = DirEntry.Err{ .original_err = err, .canonical_error = err },
+ };
+ return &temp_entries_option;
}
- pub fn readDirectory(fs: *RealFS, dir: string) !EntriesOption {
+
+ threadlocal var temp_entries_option: EntriesOption = undefined;
+
+ pub fn readDirectory(fs: *RealFS, dir: string, _handle: ?std.fs.Dir, recursive: bool) !*EntriesOption {
+ var cache_result: ?allocators.Result = null;
+
if (!fs.do_not_cache_entries) {
fs.entries_mutex.lock();
defer fs.entries_mutex.unlock();
- // First, check the cache
- if (fs.entries.get(dir)) |_dir| {
- return _dir;
+ cache_result = try fs.entries.getOrPut(dir);
+
+ if (cache_result.?.hasCheckedIfExists()) {
+ if (fs.entries.atIndex(cache_result.?.index)) |cached_result| {
+ return cached_result;
+ }
+ }
+ }
+
+ var handle = _handle orelse try fs.openDir(dir);
+
+ defer {
+ if (_handle == null) {
+ handle.close();
}
}
// Cache miss: read the directory entries
- const entries = fs.readdir(dir) catch |err| {
- _ = fs.readDirectoryError(dir, err) catch {};
- return err;
+ const entries = fs.readdir(
+ dir,
+ handle,
+ ) catch |err| {
+ return fs.readDirectoryError(dir, err) catch unreachable;
};
if (fs.watcher) |*watcher| {
@@ -409,16 +469,23 @@ pub const FileSystem = struct {
WatchData{ .dir_entries = names, .state = .dir_has_entries },
);
}
-
- fs.entries_mutex.lock();
- defer fs.entries_mutex.unlock();
- const result = EntriesOption{
- .entries = entries,
- };
if (!fs.do_not_cache_entries) {
- try fs.entries.put(dir, result);
+ fs.entries_mutex.lock();
+ defer fs.entries_mutex.unlock();
+ const result = EntriesOption{
+ .entries = entries,
+ };
+
+ var entries_ptr = try fs.entries.put(dir, true, &cache_result.?, result);
+ const dir_key = fs.entries.keyAtIndex(cache_result.?.index) orelse unreachable;
+ entries_ptr.entries.updateDir(dir_key);
+ return entries_ptr;
}
- return result;
+
+ temp_entries_option = EntriesOption{ .entries = entries };
+ temp_entries_option.entries.updateDir(try fs.allocator.dupe(u8, dir));
+
+ return &temp_entries_option;
}
fn readFileError(fs: *RealFS, path: string, err: anyerror) void {
@@ -622,6 +689,7 @@ pub const PathName = struct {
};
threadlocal var normalize_buf: [1024]u8 = undefined;
+threadlocal var join_buf: [1024]u8 = undefined;
pub const Path = struct {
pretty: string,
@@ -634,35 +702,6 @@ pub const Path = struct {
return try std.fmt.allocPrint(allocator, "{s}://{s}", .{ p.namespace, p.text });
}
- // for now, assume you won't try to normalize a path longer than 1024 chars
- pub fn normalize(str: string, allocator: *std.mem.Allocator) string {
- if (str.len == 0 or (str.len == 1 and str[0] == ' ')) return ".";
- if (resolvePath(&normalize_buf, str)) |out| {
- return allocator.dupe(u8, out) catch unreachable;
- }
- return str;
- }
-
- // for now, assume you won't try to normalize a path longer than 1024 chars
- pub fn normalizeNoAlloc(str: string, comptime remap_windows_paths: bool) string {
- if (str.len == 0 or (str.len == 1 and (str[0] == ' ' or str[0] == '\\'))) return ".";
-
- if (remap_windows_paths) {
- std.mem.copy(u8, &normalize_buf, str);
- var i: usize = 0;
- while (i < str.len) : (i += 1) {
- if (str[i] == '\\') {
- normalize_buf[i] = '/';
- }
- }
- }
-
- if (resolvePath(&normalize_buf, str)) |out| {
- return out;
- }
- return str;
- }
-
pub fn init(text: string) Path {
return Path{ .pretty = text, .text = text, .namespace = "file", .name = PathName.init(text) };
}
diff --git a/src/js_lexer.zig b/src/js_lexer.zig
index 89e2e06dd..96b6f6835 100644
--- a/src/js_lexer.zig
+++ b/src/js_lexer.zig
@@ -275,7 +275,6 @@ pub const Lexer = struct {
}
// Reset string literal
- lexer.string_literal = &([_]u16{});
lexer.string_literal_slice = lexer.source.contents[lexer.start + 1 .. lexer.end - suffixLen];
lexer.string_literal_is_ascii = !needs_slow_path;
lexer.string_literal_buffer.shrinkRetainingCapacity(0);
@@ -283,8 +282,6 @@ pub const Lexer = struct {
lexer.string_literal_buffer.ensureTotalCapacity(lexer.string_literal_slice.len) catch unreachable;
var slice = lexer.string_literal_buffer.allocatedSlice();
lexer.string_literal_buffer.items = slice[0..strings.toUTF16Buf(lexer.string_literal_slice, slice)];
- lexer.string_literal = lexer.string_literal_buffer.items;
- lexer.string_literal_slice = &[_]u8{};
}
if (quote == '\'' and lexer.json_options != null) {
@@ -483,6 +480,7 @@ pub const Lexer = struct {
if (lexer.code_point == '\\') {
try lexer.scanIdentifierWithEscapes();
lexer.token = T.t_private_identifier;
+
// lexer.Identifier, lexer.Token = lexer.scanIdentifierWithEscapes(normalIdentifier);
} else {
lexer.token = T.t_private_identifier;
@@ -766,7 +764,6 @@ pub const Lexer = struct {
lexer.token = .t_slash_equals;
},
'/' => {
- try lexer.step();
singleLineComment: while (true) {
try lexer.step();
switch (lexer.code_point) {
@@ -1440,14 +1437,12 @@ pub const Lexer = struct {
lexer.token = .t_string_literal;
lexer.string_literal_slice = lexer.source.contents[lexer.start + 1 .. lexer.end - 1];
- lexer.string_literal.len = lexer.string_literal_slice.len;
lexer.string_literal_is_ascii = !needs_decode;
- lexer.string_literal_buffer.shrinkRetainingCapacity(0);
+ lexer.string_literal_buffer.clearRetainingCapacity();
if (needs_decode) {
lexer.string_literal_buffer.ensureTotalCapacity(lexer.string_literal_slice.len) catch unreachable;
try lexer.decodeJSXEntities(lexer.string_literal_slice, &lexer.string_literal_buffer);
lexer.string_literal = lexer.string_literal_buffer.items;
- lexer.string_literal_slice = &([_]u8{0});
}
}
diff --git a/src/js_parser/js_parser.zig b/src/js_parser/js_parser.zig
index 39860b42e..641fa312d 100644
--- a/src/js_parser/js_parser.zig
+++ b/src/js_parser/js_parser.zig
@@ -533,25 +533,27 @@ pub const SideEffects = enum {
equality.ok = equality.equal;
},
.e_undefined => |l| {
- equality.equal = @as(Expr.Tag, right) == Expr.Tag.e_undefined;
- equality.ok = equality.equal;
+ equality.ok = @as(Expr.Tag, right) == Expr.Tag.e_undefined;
+ equality.equal = equality.ok;
},
.e_boolean => |l| {
equality.ok = @as(Expr.Tag, right) == Expr.Tag.e_boolean;
- equality.equal = l.value == right.e_boolean.value;
+ equality.equal = equality.ok and l.value == right.e_boolean.value;
},
.e_number => |l| {
equality.ok = @as(Expr.Tag, right) == Expr.Tag.e_number;
- equality.equal = l.value == right.e_number.value;
+ equality.equal = equality.ok and l.value == right.e_number.value;
},
.e_big_int => |l| {
equality.ok = @as(Expr.Tag, right) == Expr.Tag.e_big_int;
- equality.equal = strings.eql(l.value, right.e_big_int.value);
+ equality.equal = equality.ok and strings.eql(l.value, right.e_big_int.value);
},
.e_string => |l| {
equality.ok = @as(Expr.Tag, right) == Expr.Tag.e_string;
- const r = right.e_string;
- equality.equal = r.eql(E.String, l);
+ if (equality.ok) {
+ const r = right.e_string;
+ equality.equal = r.eql(E.String, l);
+ }
},
else => {},
}
@@ -790,7 +792,7 @@ pub const SideEffects = enum {
return Result{ .ok = true, .value = !strings.eqlComptime(e.value, "0"), .side_effects = .no_side_effects };
},
.e_string => |e| {
- return Result{ .ok = true, .value = e.value.len > 0, .side_effects = .no_side_effects };
+ return Result{ .ok = true, .value = std.math.max(e.value.len, e.utf8.len) > 0, .side_effects = .no_side_effects };
},
.e_function, .e_arrow, .e_reg_exp => {
return Result{ .ok = true, .value = true, .side_effects = .no_side_effects };
@@ -1790,7 +1792,7 @@ pub const P = struct {
}
const str = arg.data.e_string;
- const import_record_index = p.addImportRecord(.dynamic, arg.loc, p.lexer.utf16ToString(str.value));
+ const import_record_index = p.addImportRecord(.dynamic, arg.loc, str.string(p.allocator) catch unreachable);
p.import_records.items[import_record_index].handles_import_errors = (state.is_await_target and p.fn_or_arrow_data_visit.try_body_count != 0) or state.is_then_catch_target;
p.import_records_for_current_part.append(import_record_index) catch unreachable;
return p.e(E.Import{
@@ -1892,13 +1894,14 @@ pub const P = struct {
}
pub fn findSymbol(p: *P, loc: logger.Loc, name: string) !FindSymbolResult {
- var ref: Ref = Ref{};
+ var ref: Ref = undefined;
var declare_loc: logger.Loc = undefined;
var is_inside_with_scope = false;
var did_forbid_argumen = false;
- var scope = p.current_scope;
+ var _scope: ?*Scope = p.current_scope;
+ var did_match = false;
- while (true) {
+ while (_scope) |scope| : (_scope = _scope.?.parent) {
// Track if we're inside a "with" statement body
if (scope.kind == .with) {
@@ -1916,19 +1919,17 @@ pub const P = struct {
if (scope.members.get(name)) |member| {
ref = member.ref;
declare_loc = member.loc;
+ did_match = true;
break;
}
+ }
- if (scope.parent) |parent| {
- scope = parent;
- } else {
- // Allocate an "unbound" symbol
- p.checkForNonBMPCodePoint(loc, name);
- ref = try p.newSymbol(.unbound, name);
- declare_loc = loc;
- try p.module_scope.members.put(name, js_ast.Scope.Member{ .ref = ref, .loc = logger.Loc.Empty });
- break;
- }
+ if (!did_match) {
+ // Allocate an "unbound" symbol
+ p.checkForNonBMPCodePoint(loc, name);
+ ref = p.newSymbol(.unbound, name) catch unreachable;
+ declare_loc = loc;
+ p.module_scope.members.put(name, js_ast.Scope.Member{ .ref = ref, .loc = logger.Loc.Empty }) catch unreachable;
}
// If we had to pass through a "with" statement body to get to the symbol
@@ -1997,9 +1998,12 @@ pub const P = struct {
// code regions since those will be culled.
if (!p.is_control_flow_dead) {
p.symbols.items[ref.inner_index].use_count_estimate += 1;
- var use = p.symbol_uses.get(ref) orelse Symbol.Use{};
- use.count_estimate += 1;
- p.symbol_uses.put(ref, use) catch unreachable;
+ var result = p.symbol_uses.getOrPut(ref) catch unreachable;
+ if (!result.found_existing) {
+ result.entry.value = Symbol.Use{ .count_estimate = 1 };
+ } else {
+ result.entry.value.count_estimate += 1;
+ }
}
// The correctness of TypeScript-to-JavaScript conversion relies on accurate
@@ -2283,8 +2287,59 @@ pub const P = struct {
if (!symbol.isHoisted()) {
continue :nextMember;
}
+
+ // Check for collisions that would prevent to hoisting "var" symbols up to the enclosing function scope
+ var __scope = scope.parent;
+
+ while (__scope) |_scope| {
+ // Variable declarations hoisted past a "with" statement may actually end
+ // up overwriting a property on the target of the "with" statement instead
+ // of initializing the variable. We must not rename them or we risk
+ // causing a behavior change.
+ //
+ // var obj = { foo: 1 }
+ // with (obj) { var foo = 2 }
+ // assert(foo === undefined)
+ // assert(obj.foo === 2)
+ //
+ if (_scope.kind == .with) {
+ symbol.must_not_be_renamed = true;
+ }
+
+ if (_scope.members.getEntry(symbol.original_name)) |existing_member_entry| {
+ const existing_member = existing_member_entry.value;
+ const existing_symbol: Symbol = p.symbols.items[existing_member.ref.inner_index];
+
+ // We can hoist the symbol from the child scope into the symbol in
+ // this scope if:
+ //
+ // - The symbol is unbound (i.e. a global variable access)
+ // - The symbol is also another hoisted variable
+ // - The symbol is a function of any kind and we're in a function or module scope
+ //
+ // Is this unbound (i.e. a global access) or also hoisted?
+ if (existing_symbol.kind == .unbound or existing_symbol.kind == .hoisted or
+ (Symbol.isKindFunction(existing_symbol.kind) and (_scope.kind == .entry or _scope.kind == .function_body)))
+ {
+ // Silently merge this symbol into the existing symbol
+ symbol.link = existing_member.ref;
+ _scope.members.put(symbol.original_name, existing_member) catch unreachable;
+ continue :nextMember;
+ }
+ }
+
+ if (_scope.kindStopsHoisting()) {
+ _scope.members.put(symbol.original_name, res.value) catch unreachable;
+ break;
+ }
+ __scope = _scope.parent;
+ }
}
}
+
+ for (scope.children.items) |_item, i| {
+ p.hoistSymbols(scope.children.items[i]);
+ }
}
pub fn nextScopeInOrderForVisitPass(p: *P) ScopeOrder {
@@ -2737,7 +2792,7 @@ pub const P = struct {
}
var parseStmtOpts = ParseStatementOptions{};
- p.declareBinding(.hoisted, arg, &parseStmtOpts) catch unreachable;
+ p.declareBinding(.hoisted, &arg, &parseStmtOpts) catch unreachable;
var default_value: ?ExprNodeIndex = null;
if (!func.flags.has_rest_arg and p.lexer.token == .t_equals) {
@@ -3523,7 +3578,7 @@ pub const P = struct {
// jarred: TIL!
if (p.lexer.token != .t_open_brace) {
try p.lexer.expect(.t_open_paren);
- const value = try p.parseBinding();
+ var value = try p.parseBinding();
// Skip over types
if (p.options.ts and p.lexer.token == .t_colon) {
@@ -3542,7 +3597,7 @@ pub const P = struct {
else => {},
}
stmtOpts = ParseStatementOptions{};
- try p.declareBinding(kind, value, &stmtOpts);
+ try p.declareBinding(kind, &value, &stmtOpts);
binding = value;
}
@@ -4533,6 +4588,8 @@ pub const P = struct {
const name = p.lexer.identifier;
const loc = p.lexer.loc();
+ const e_str = p.lexer.toEString();
+
if (!p.lexer.isIdentifierOrKeyword()) {
try p.lexer.expect(.t_identifier);
}
@@ -4541,7 +4598,7 @@ pub const P = struct {
const ref = p.storeNameInRef(name) catch unreachable;
- key = p.e(p.lexer.toEString(), loc);
+ key = p.e(e_str, loc);
if (p.lexer.token != .t_colon and p.lexer.token != .t_open_paren) {
const value = p.b(B.Identifier{ .ref = ref }, loc);
@@ -4590,7 +4647,7 @@ pub const P = struct {
var value: ?js_ast.Expr = null;
var local = try p.parseBinding();
- p.declareBinding(kind, local, opts) catch unreachable;
+ p.declareBinding(kind, &local, opts) catch unreachable;
// Skip over types
if (p.options.ts) {
@@ -5097,9 +5154,9 @@ pub const P = struct {
try p.lexer.expect(T.t_equals_greater_than);
- for (args) |arg| {
+ for (args) |*arg| {
var opts = ParseStatementOptions{};
- try p.declareBinding(Symbol.Kind.hoisted, arg.binding, &opts);
+ try p.declareBinding(Symbol.Kind.hoisted, &arg.binding, &opts);
}
// The ability to call "super()" is inherited by arrow functions
@@ -5125,7 +5182,7 @@ pub const P = struct {
return E.Arrow{ .args = args, .prefer_expr = true, .body = G.FnBody{ .loc = arrow_loc, .stmts = stmts } };
}
- pub fn declareBinding(p: *P, kind: Symbol.Kind, binding: BindingNodeIndex, opts: *ParseStatementOptions) !void {
+ pub fn declareBinding(p: *P, kind: Symbol.Kind, binding: *BindingNodeIndex, opts: *ParseStatementOptions) !void {
switch (binding.data) {
.b_missing => {},
.b_identifier => |bind| {
@@ -5135,15 +5192,14 @@ pub const P = struct {
},
.b_array => |bind| {
- for (bind.items) |item| {
- p.declareBinding(kind, item.binding, opts) catch unreachable;
+ for (bind.items) |item, i| {
+ p.declareBinding(kind, &bind.items[i].binding, opts) catch unreachable;
}
},
.b_object => |bind| {
for (bind.properties) |*prop| {
- const value = prop.value;
- p.declareBinding(kind, value, opts) catch unreachable;
+ p.declareBinding(kind, &prop.value, opts) catch unreachable;
}
},
@@ -5571,7 +5627,7 @@ pub const P = struct {
}
}
- key = p.e(p.lexer.toEString(), name_range.loc);
+ key = p.e(E.String{ .utf8 = name }, name_range.loc);
// Parse a shorthand property
if (!opts.is_class and kind == .normal and p.lexer.token != .t_colon and p.lexer.token != .t_open_paren and p.lexer.token != .t_less_than and !opts.is_generator and !js_lexer.Keywords.has(name)) {
@@ -5691,7 +5747,7 @@ pub const P = struct {
if (opts.is_class and !is_computed) {
switch (key.data) {
.e_string => |str| {
- if (!opts.is_static and strings.eqlUtf16("constructor", str.value)) {
+ if (!opts.is_static and str.eql(string, "constructor")) {
if (kind == .get) {
p.log.addRangeError(p.source, key_range, "Class constructor cannot be a getter") catch unreachable;
} else if (kind == .set) {
@@ -5703,7 +5759,7 @@ pub const P = struct {
} else {
is_constructor = true;
}
- } else if (opts.is_static and strings.eqlUtf16("prototype", str.value)) {
+ } else if (opts.is_static and str.eql(string, "prototype")) {
p.log.addRangeError(p.source, key_range, "Invalid static method name \"prototype\"") catch unreachable;
}
},
@@ -5894,7 +5950,7 @@ pub const P = struct {
if (opts.ts_decorators.len > 0) {
switch ((property.key orelse p.panic("Internal error: Expected property {s} to have a key.", .{property})).data) {
.e_string => |str| {
- if (strings.eqlUtf16("constructor", str.value)) {
+ if (str.eql(string, "constructor")) {
p.log.addError(p.source, first_decorator_loc, "TypeScript does not allow decorators on class constructors") catch unreachable;
}
},
@@ -8586,7 +8642,7 @@ pub const P = struct {
in.assign_target,
is_delete_target,
e_.target,
- if (e_.index.data.e_string.isUTF8()) p.lexer.utf16ToString(e_.index.data.e_string.value) else e_.index.data.e_string.utf8,
+ e_.index.data.e_string.string(p.allocator) catch unreachable,
e_.index.loc,
is_call_target,
)) |val| {
@@ -8598,7 +8654,7 @@ pub const P = struct {
// though this is a run-time error, we make it a compile-time error when
// bundling because scope hoisting means these will no longer be run-time
// errors.
- if ((in.assign_target != .none or is_delete_target) and @as(Expr.Tag, e_.target.data) == .e_identifier) {
+ if ((in.assign_target != .none or is_delete_target) and @as(Expr.Tag, e_.target.data) == .e_identifier and p.symbols.items[e_.target.data.e_identifier.ref.inner_index].kind == .import) {
const r = js_lexer.rangeOfIdentifier(p.source, e_.target.loc);
p.log.addRangeErrorFmt(
p.source,
@@ -8818,14 +8874,10 @@ pub const P = struct {
var has_spread = false;
var has_proto = false;
- var i: usize = 0;
- while (i < e_.properties.len) : (i += 1) {
- var property = e_.properties[i];
-
+ for (e_.properties) |*property, i| {
if (property.kind != .spread) {
- const key = p.visitExpr(property.key orelse Global.panic("Expected property key", .{}));
- e_.properties[i].key = key;
-
+ property.key = p.visitExpr(property.key orelse Global.panic("Expected property key", .{}));
+ const key = property.key.?;
// Forbid duplicate "__proto__" properties according to the specification
if (!property.flags.is_computed and !property.flags.was_shorthand and !property.flags.is_method and in.assign_target == .none and key.data.isStringValue() and strings.eqlComptime(
// __proto__ is utf8, assume it lives in refs
@@ -8873,9 +8925,6 @@ pub const P = struct {
}
}
}
-
- // TODO: can we avoid htis copy
- e_.properties[i] = property;
}
},
.e_import => |e_| {
@@ -9208,7 +9257,7 @@ pub const P = struct {
for (ex.properties) |property| {
// The key must still be evaluated if it's computed or a spread
- if (property.kind == .spread or property.flags.is_computed) {
+ if (property.kind == .spread or property.flags.is_computed or property.flags.is_spread) {
return false;
}
@@ -9554,9 +9603,12 @@ pub const P = struct {
if (data.label) |*label| {
const name = p.loadNameFromRef(label.ref orelse p.panic("Expected label to have a ref", .{}));
const res = p.findLabelSymbol(label.loc, name);
-
- label.ref = res.ref;
- } else if (p.fn_or_arrow_data_visit.is_inside_loop and !p.fn_or_arrow_data_visit.is_inside_switch) {
+ if (res.found) {
+ label.ref = res.ref;
+ } else {
+ data.label = null;
+ }
+ } else if (!p.fn_or_arrow_data_visit.is_inside_loop and !p.fn_or_arrow_data_visit.is_inside_switch) {
const r = js_lexer.rangeOfIdentifier(p.source, stmt.loc);
p.log.addRangeError(p.source, r, "Cannot use \"break\" here") catch unreachable;
}
@@ -10289,9 +10341,7 @@ pub const P = struct {
}
},
.b_object => |bind| {
- var i: usize = 0;
- while (i < bind.properties.len) : (i += 1) {
- var property = bind.properties[i];
+ for (bind.properties) |*property| {
if (!property.flags.is_spread) {
property.key = p.visitExpr(property.key);
}
@@ -10312,7 +10362,6 @@ pub const P = struct {
else => {},
}
}
- bind.properties[i] = property;
}
},
else => {
@@ -10377,19 +10426,17 @@ pub const P = struct {
var _scope: ?*Scope = p.current_scope;
- while (_scope) |scope| : (_scope = scope.parent) {
- var label_ref = scope.label_ref orelse continue;
-
- if (!scope.kindStopsHoisting() or (scope.kind != .label) or !strings.eql(name, p.symbols.items[label_ref.inner_index].original_name)) {
- continue;
+ while (_scope != null and !_scope.?.kindStopsHoisting()) : (_scope = _scope.?.parent.?) {
+ const scope = _scope orelse unreachable;
+ const label_ref = scope.label_ref orelse continue;
+ if (scope.kind == .label and strings.eql(name, p.symbols.items[label_ref.inner_index].original_name)) {
+ // Track how many times we've referenced this symbol
+ p.recordUsage(label_ref);
+ res.ref = label_ref;
+ res.is_loop = scope.label_stmt_is_loop;
+ res.found = true;
+ return res;
}
-
- // Track how many times we've referenced this symbol
- p.recordUsage(label_ref);
- res.ref = label_ref;
- res.is_loop = scope.label_stmt_is_loop;
- res.found = true;
- break;
}
const r = js_lexer.rangeOfIdentifier(p.source, loc);
@@ -10471,12 +10518,7 @@ pub const P = struct {
if (is_private) {} else if (!property.flags.is_method and !property.flags.is_computed) {
if (property.key) |key| {
if (@as(Expr.Tag, key.data) == .e_string) {
- const str = key.data.e_string;
- if (str.isUTF8()) {
- name_to_keep = p.lexer.utf16ToString(key.data.e_string.value);
- } else {
- name_to_keep = str.utf8;
- }
+ name_to_keep = key.data.e_string.string(p.allocator) catch unreachable;
}
}
}
@@ -10869,8 +10911,8 @@ pub const P = struct {
// with no statements
while (i < parts.len) : (i += 1) {
var part = parts[i];
- _ = p.import_records_for_current_part.toOwnedSlice();
- _ = p.declared_symbols.toOwnedSlice();
+ p.import_records_for_current_part.shrinkRetainingCapacity(0);
+ p.declared_symbols.shrinkRetainingCapacity(0);
var result = try ImportScanner.scan(p, part.stmts);
kept_import_equals = kept_import_equals or result.kept_import_equals;
@@ -10898,6 +10940,22 @@ pub const P = struct {
}
parts = parts[0..parts_end];
+ // Do a second pass for exported items now that imported items are filled out
+ for (parts) |part| {
+ for (part.stmts) |stmt| {
+ switch (stmt.data) {
+ .s_export_clause => |clause| {
+ for (clause.items) |item| {
+ if (p.named_imports.getEntry(item.name.ref.?)) |_import| {
+ _import.value.is_exported = true;
+ }
+ }
+ },
+ else => {},
+ }
+ }
+ }
+
// Analyze cross-part dependencies for tree shaking and code splitting
{
diff --git a/src/js_printer.zig b/src/js_printer.zig
index a0142983f..e72eefbde 100644
--- a/src/js_printer.zig
+++ b/src/js_printer.zig
@@ -1229,8 +1229,7 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
p.options.indent += 1;
}
- var i: usize = 0;
- while (i < e.properties.len) : (i += 1) {
+ for (e.properties) |property, i| {
if (i != 0) {
p.print(",");
if (e.is_single_line) {
@@ -1242,7 +1241,7 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
p.printNewline();
p.printIndent();
}
- p.printProperty(e.properties[i]);
+ p.printProperty(property);
}
if (!e.is_single_line) {
@@ -1642,6 +1641,7 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
p.printExpr(item.value.?, .comma, ExprFlag.None());
return;
}
+ const _key = item.key orelse unreachable;
if (item.flags.is_static) {
p.print("static");
@@ -1686,7 +1686,7 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
if (item.flags.is_computed) {
p.print("[");
- p.printExpr(item.key.?, .comma, ExprFlag.None());
+ p.printExpr(_key, .comma, ExprFlag.None());
p.print("]");
if (item.value) |val| {
@@ -1711,12 +1711,12 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
return;
}
- switch (item.key.?.data) {
+ switch (_key.data) {
.e_private_identifier => |key| {
p.printSymbol(key.ref);
},
.e_string => |key| {
- p.addSourceMapping(item.key.?.loc);
+ p.addSourceMapping(_key.loc);
if (key.isUTF8()) {
p.printSpaceBeforeIdentifier();
p.printIdentifier(key.utf8);
@@ -1786,14 +1786,21 @@ pub fn NewPrinter(comptime ascii_only: bool) type {
}
}
} else {
- const c = p.bestQuoteCharForString(key.value, false);
- p.print(c);
- p.printQuotedUTF16(key.value, c);
- p.print(c);
+ if (key.isUTF8()) {
+ const c = p.bestQuoteCharForString(key.utf8, false);
+ p.print(c);
+ p.printIdentifier(key.utf8);
+ p.print(c);
+ } else {
+ const c = p.bestQuoteCharForString(key.value, false);
+ p.print(c);
+ p.printQuotedUTF16(key.value, c);
+ p.print(c);
+ }
}
},
else => {
- p.printExpr(item.key.?, .lowest, ExprFlag{});
+ p.printExpr(_key, .lowest, ExprFlag{});
},
}
diff --git a/src/json_parser.zig b/src/json_parser.zig
index ee28c2a93..eb51c2e53 100644
--- a/src/json_parser.zig
+++ b/src/json_parser.zig
@@ -93,17 +93,7 @@ fn JSONLikeParser(opts: js_lexer.JSONOptions) type {
return p.e(E.Null{}, loc);
},
.t_string_literal => {
- var str: E.String = undefined;
- if (p.lexer.string_literal_is_ascii) {
- str = E.String{
- .utf8 = p.lexer.string_literal_slice,
- };
- } else {
- const value = p.lexer.stringLiteralUTF16();
- str = E.String{
- .value = value,
- };
- }
+ var str: E.String = p.lexer.toEString();
try p.lexer.next();
return p.e(str, loc);
diff --git a/src/resolver/package_json.zig b/src/resolver/package_json.zig
index 68d6bacb1..ea62c81cf 100644
--- a/src/resolver/package_json.zig
+++ b/src/resolver/package_json.zig
@@ -50,7 +50,10 @@ pub const PackageJSON = struct {
errdefer r.allocator.free(package_json_path);
const entry = r.caches.fs.readFile(r.fs, input_path) catch |err| {
- r.log.addErrorFmt(null, logger.Loc.Empty, r.allocator, "Cannot read file \"{s}\": {s}", .{ r.prettyPath(fs.Path.init(input_path)), @errorName(err) }) catch unreachable;
+ if (err != error.IsDir) {
+ r.log.addErrorFmt(null, logger.Loc.Empty, r.allocator, "Cannot read file \"{s}\": {s}", .{ r.prettyPath(fs.Path.init(input_path)), @errorName(err) }) catch unreachable;
+ }
+
return null;
};
@@ -146,7 +149,7 @@ pub const PackageJSON = struct {
// import of "foo", but that's actually not a bug. Or arguably it's a
// bug in Browserify but we have to replicate this bug because packages
// do this in the wild.
- const key = fs.Path.normalize(_key_str, r.allocator);
+ const key = r.allocator.dupe(u8, r.fs.normalize(_key_str)) catch unreachable;
switch (value.data) {
.e_string => |str| {
diff --git a/src/resolver/resolve_path.zig b/src/resolver/resolve_path.zig
index f639bff1b..9cb3e635c 100644
--- a/src/resolver/resolve_path.zig
+++ b/src/resolver/resolve_path.zig
@@ -1,83 +1,498 @@
-// https://github.com/MasterQ32/ftz/blob/3183b582211f8e38c1c3363c56753026ca45c11f/src/main.zig#L431-L509
-// Thanks, Felix! We should get this into std perhaps.
+const tester = @import("../test/tester.zig");
const std = @import("std");
-/// Resolves a unix-like path and removes all "." and ".." from it. Will not escape the root and can be used to sanitize inputs.
-pub fn resolvePath(buffer: []u8, src_path: []const u8) ?[]u8 {
- var end: usize = 0;
- buffer[0] = '.';
+threadlocal var parser_join_input_buffer: [1024]u8 = undefined;
+threadlocal var parser_buffer: [1024]u8 = undefined;
- var iter = std.mem.tokenize(src_path, "/");
- while (iter.next()) |segment| {
- if (end >= buffer.len) break;
+// This function is based on Node.js' path.normalize function.
+// https://github.com/nodejs/node/blob/36bb31be5f0b85a0f6cbcb36b64feb3a12c60984/lib/path.js#L66
+pub fn normalizeStringGeneric(str: []const u8, buf: []u8, comptime allow_above_root: bool, comptime separator: u8, comptime isPathSeparator: anytype, lastIndexOfSeparator: anytype) []u8 {
+ var i: usize = 0;
+ var last_segment_length: i32 = 0;
+ var last_slash: i32 = -1;
+ var dots: i32 = 0;
+ var code: u8 = 0;
- if (std.mem.eql(u8, segment, ".")) {
- continue;
- } else if (std.mem.eql(u8, segment, "..")) {
- while (true) {
- if (end == 0)
- break;
- if (buffer[end] == '/') {
- break;
+ var written_len: usize = 0;
+ const stop_len = str.len;
+
+ while (i <= stop_len) : (i += 1) {
+ if (i < stop_len) {
+ code = str[i];
+ } else if (@call(std.builtin.CallOptions{ .modifier = .always_inline }, isPathSeparator, .{code})) {
+ break;
+ } else {
+ code = separator;
+ }
+
+ if (@call(std.builtin.CallOptions{ .modifier = .always_inline }, isPathSeparator, .{code})) {
+ if (last_slash == @intCast(i32, i) - 1 or dots == 1) {
+ // NOOP
+ } else if (dots == 2) {
+ if (written_len < 2 or last_segment_length != 2 or buf[written_len - 1] != '.' or buf[written_len - 2] != '.') {
+ if (written_len > 2) {
+ if (lastIndexOfSeparator(buf[0..written_len])) |last_slash_index| {
+ written_len = last_slash_index;
+ last_segment_length = @intCast(i32, written_len - 1 - (lastIndexOfSeparator(buf[0..written_len]) orelse 0));
+ } else {
+ written_len = 0;
+ }
+ last_slash = @intCast(i32, i);
+ dots = 0;
+ continue;
+ } else if (written_len != 0) {
+ written_len = 0;
+ last_segment_length = 0;
+ last_slash = @intCast(i32, i);
+ dots = 0;
+ continue;
+ }
+
+ if (allow_above_root) {
+ if (written_len > 0) {
+ buf[written_len] = separator;
+ written_len += 1;
+ }
+
+ buf[written_len] = '.';
+ written_len += 1;
+ buf[written_len] = '.';
+ written_len += 1;
+
+ last_segment_length = 2;
+ }
+ }
+ } else {
+ if (written_len > 0) {
+ buf[written_len] = separator;
+ written_len += 1;
}
- end -= 1;
+
+ const slice = str[@intCast(usize, @intCast(usize, last_slash + 1))..i];
+ std.mem.copy(u8, buf[written_len .. written_len + slice.len], slice);
+ written_len += slice.len;
+ last_segment_length = @intCast(i32, i) - last_slash - 1;
}
+
+ last_slash = @intCast(i32, i);
+ dots = 0;
+ } else if (code == '.' and dots != -1) {
+ dots += 1;
} else {
- if (end + segment.len + 1 > buffer.len)
+ dots = -1;
+ }
+ }
+
+ return buf[0..written_len];
+}
+
+pub const Platform = enum {
+ auto,
+ loose,
+ windows,
+ posix,
+
+ pub fn isSeparator(comptime _platform: Platform, char: u8) bool {
+ const platform = _platform.resolve();
+ switch (platform) {
+ .auto => unreachable,
+ .loose => {
+ return isSepAny(char);
+ },
+ .windows => {
+ return isSepWin32(char);
+ },
+ .posix => {
+ return isSepPosix(char);
+ },
+ }
+ }
+
+ pub fn leadingSeparatorIndex(comptime _platform: Platform, path: anytype) ?usize {
+ switch (_platform.resolve()) {
+ .windows => {
+ if (path.len < 1)
+ return null;
+
+ if (path[0] == '/')
+ return 0;
+
+ if (path[0] == '\\')
+ return 0;
+
+ if (path.len < 3)
+ return null;
+
+ // C:\
+ // C:/
+ if (path[0] >= 'A' and path[0] <= 'Z' and path[1] == ':') {
+ if (path[2] == '/')
+ return 2;
+ if (path[2] == '\\')
+ return 2;
+ }
+
return null;
+ },
+ .posix => {
+ if (path.len > 0 and path[0] == '/') {
+ return 0;
+ } else {
+ return null;
+ }
+ },
+ else => {
+ return leadingSeparatorIndex(.windows, path) orelse leadingSeparatorIndex(.posix, path);
+ },
+ }
+ }
+
+ pub fn resolve(comptime _platform: Platform) Platform {
+ if (_platform == .auto) {
+ switch (std.Target.current.os.tag) {
+ .windows => {
+ return .windows;
+ },
+
+ .freestanding, .emscripten, .other => {
+ return .loose;
+ },
- const start = end;
- buffer[end] = '/';
- end += segment.len + 1;
- std.mem.copy(u8, buffer[start + 1 .. end], segment);
+ else => {
+ return .posix;
+ },
+ }
}
+
+ return _platform;
+ }
+};
+
+pub fn normalizeString(str: []const u8, comptime allow_above_root: bool, comptime _platform: Platform) []u8 {
+ return normalizeStringBuf(str, &parser_buffer, allow_above_root, _platform);
+}
+
+pub fn normalizeStringBuf(str: []const u8, buf: []u8, comptime allow_above_root: bool, comptime _platform: Platform) []u8 {
+ comptime const platform = _platform.resolve();
+
+ switch (platform) {
+ .auto => unreachable,
+
+ .windows => {
+ return normalizeStringWindowsBuf(str, buf, allow_above_root);
+ },
+ .posix => {
+ return normalizeStringPosixBuf(str, buf, allow_above_root);
+ },
+
+ .loose => {
+ return normalizeStringLooseBuf(str, buf, allow_above_root);
+ },
+ }
+}
+
+pub fn normalizeStringAlloc(allocator: *std.mem.Allocator, str: []const u8, comptime allow_above_root: bool, comptime _platform: Platform) ![]const u8 {
+ return try allocator.dupe(u8, normalizeString(str, allow_above_root, _platform));
+}
+
+pub fn normalizeAndJoin2(_cwd: []const u8, comptime _platform: Platform, part: anytype, part2: anytype) []const u8 {
+ const parts = [_][]const u8{ part, part2 };
+ const slice = normalizeAndJoinString(_cwd, &parts, _platform);
+ return slice;
+}
+
+pub fn normalizeAndJoin(_cwd: []const u8, comptime _platform: Platform, part: anytype) []const u8 {
+ const parts = [_][]const u8{
+ part,
+ };
+ const slice = normalizeAndJoinString(_cwd, &parts, _platform);
+ return slice;
+}
+
+// Convert parts of potentially invalid file paths into a single valid filpeath
+// without querying the filesystem
+// This is the equivalent of
+pub fn normalizeAndJoinString(_cwd: []const u8, parts: anytype, comptime _platform: Platform) []const u8 {
+ return normalizeAndJoinStringBuf(_cwd, &parser_join_input_buffer, parts, _platform);
+}
+
+pub fn normalizeAndJoinStringBuf(_cwd: []const u8, buf: []u8, parts: anytype, comptime _platform: Platform) []const u8 {
+ if (parts.len == 0) {
+ return _cwd;
+ }
+
+ if ((_platform == .loose or _platform == .posix) and parts.len == 1 and parts[0].len == 1 and parts[0] == std.fs.path.sep_posix) {
+ return "/";
+ }
+
+ var cwd = _cwd;
+ var out: usize = 0;
+ // When parts[0] is absolute, we treat that as, effectively, the cwd
+ var ignore_cwd = cwd.len == 0;
+
+ // Windows leading separators can be a lot of things...
+ // So we need to do this instead of just checking the first char.
+ var leading_separator: []const u8 = "";
+ if (_platform.leadingSeparatorIndex(parts[0])) |leading_separator_i| {
+ leading_separator = parts[0][0 .. leading_separator_i + 1];
+ ignore_cwd = true;
}
- const result = if (end == 0)
- buffer[0 .. end + 1]
- else
- buffer[0..end];
+ if (!ignore_cwd) {
+ leading_separator = cwd[0 .. 1 + (_platform.leadingSeparatorIndex(_cwd) orelse unreachable)]; // cwd must be absolute
+ cwd = _cwd[leading_separator.len..cwd.len];
+ out = cwd.len;
+ std.debug.assert(out < buf.len);
+ std.mem.copy(u8, buf[0..out], cwd);
+ }
+
+ for (parts) |part, i| {
+ // This never returns leading separators.
+ var normalized_part = normalizeString(part, true, _platform);
+ if (normalized_part.len == 0) {
+ continue;
+ }
+ switch (_platform.resolve()) {
+ .windows => {
+ buf[out] = std.fs.path.sep_windows;
+ },
+ else => {
+ buf[out] = std.fs.path.sep_posix;
+ },
+ }
+
+ out += 1;
- if (std.mem.eql(u8, result, src_path)) {
- return null;
+ const start = out;
+ out += normalized_part.len;
+ std.debug.assert(out < buf.len);
+ std.mem.copy(u8, buf[start..out], normalized_part);
}
- return result;
+ // One last normalization, to remove any ../ added
+ const result = normalizeStringBuf(buf[0..out], parser_buffer[leading_separator.len..parser_buffer.len], false, _platform);
+ std.mem.copy(u8, buf[0..leading_separator.len], leading_separator);
+ std.mem.copy(u8, buf[leading_separator.len .. result.len + leading_separator.len], result);
+
+ return buf[0 .. result.len + leading_separator.len];
+}
+
+pub fn isSepPosix(char: u8) bool {
+ return char == std.fs.path.sep_posix;
+}
+
+pub fn isSepWin32(char: u8) bool {
+ return char == std.fs.path.sep_windows;
+}
+
+pub fn isSepAny(char: u8) bool {
+ return @call(.{ .modifier = .always_inline }, isSepPosix, .{char}) or @call(.{ .modifier = .always_inline }, isSepWin32, .{char});
}
-fn testResolve(expected: []const u8, input: []const u8) !void {
- var buffer: [1024]u8 = undefined;
+pub fn lastIndexOfSeparatorWindows(slice: []const u8) ?usize {
+ return std.mem.lastIndexOfScalar(u8, slice, std.fs.path.sep_windows);
+}
- const actual = try resolvePath(&buffer, input);
- std.testing.expectEqualStrings(expected, actual);
+pub fn lastIndexOfSeparatorPosix(slice: []const u8) ?usize {
+ return std.mem.lastIndexOfScalar(u8, slice, std.fs.path.sep_posix);
}
-test "resolvePath" {
- try testResolve("/", "");
- try testResolve("/", "/");
- try testResolve("/", "////////////");
+pub fn lastIndexOfSeparatorLoose(slice: []const u8) ?usize {
+ return std.mem.lastIndexOfAny(u8, slice, "/\\");
+}
- try testResolve("/a", "a");
- try testResolve("/a", "/a");
- try testResolve("/a", "////////////a");
- try testResolve("/a", "////////////a///");
+pub fn normalizeStringPosix(str: []const u8, comptime allow_above_root: bool) []u8 {
+ return normalizeStringGenericBuf(str, &parser_buffer, allow_above_root, std.fs.path.sep_posix, isSepPosix, lastIndexOfSeparatorPosix);
+}
- try testResolve("/a/b/c/d", "/a/b/c/d");
+pub fn normalizeStringPosixBuf(str: []const u8, buf: []u8, comptime allow_above_root: bool) []u8 {
+ return normalizeStringGeneric(str, buf, allow_above_root, std.fs.path.sep_posix, isSepPosix, lastIndexOfSeparatorPosix);
+}
- try testResolve("/a/b/d", "/a/b/c/../d");
+pub fn normalizeStringWindows(str: []const u8, comptime allow_above_root: bool) []u8 {
+ return normalizeStringGenericBuf(str, &parser_buffer, allow_above_root, std.fs.path.sep_windows, isSepWin32, lastIndexOfSeparatorWindows);
+}
+
+pub fn normalizeStringWindowsBuf(str: []const u8, buf: []u8, comptime allow_above_root: bool) []u8 {
+ return normalizeStringGeneric(str, buf, allow_above_root, std.fs.path.sep_windows, isSepWin32, lastIndexOfSeparatorWindows);
+}
+
+pub fn normalizeStringLoose(str: []const u8, comptime allow_above_root: bool) []u8 {
+ return normalizeStringGenericBuf(str, &parser_buffer, allow_above_root, std.fs.path.sep_posix, isSepAny, lastIndexOfSeparatorLoose);
+}
+
+pub fn normalizeStringLooseBuf(str: []const u8, buf: []u8, comptime allow_above_root: bool) []u8 {
+ return normalizeStringGeneric(str, buf, allow_above_root, std.fs.path.sep_posix, isSepAny, lastIndexOfSeparatorLoose);
+}
+
+test "normalizeAndJoinStringPosix" {
+ var t = tester.Tester.t(std.heap.c_allocator);
+ defer t.report(@src());
+ const string = []const u8;
+ const cwd = "/Users/jarredsumner/Code/app";
+
+ _ = t.expect(
+ "/Users/jarredsumner/Code/app/foo/bar/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "foo", "bar", "file.js" }, .posix),
+ @src(),
+ );
+ _ = t.expect(
+ "/Users/jarredsumner/Code/app/foo/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "foo", "bar", "../file.js" }, .posix),
+ @src(),
+ );
+ _ = t.expect(
+ "/Users/jarredsumner/Code/app/foo/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "foo", "./bar", "../file.js" }, .posix),
+ @src(),
+ );
+
+ _ = t.expect(
+ "/Users/jarredsumner/Code/app/foo/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "././././foo", "././././bar././././", "../file.js" }, .posix),
+ @src(),
+ );
+ _ = t.expect(
+ "/Code/app/foo/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "/Code/app", "././././foo", "././././bar././././", "../file.js" }, .posix),
+ @src(),
+ );
+
+ _ = t.expect(
+ "/Code/app/foo/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "/Code/app", "././././foo", ".", "././././bar././././", ".", "../file.js" }, .posix),
+ @src(),
+ );
+
+ _ = t.expect(
+ "/Code/app/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "/Code/app", "././././foo", "..", "././././bar././././", ".", "../file.js" }, .posix),
+ @src(),
+ );
+}
+
+test "normalizeAndJoinStringLoose" {
+ var t = tester.Tester.t(std.heap.c_allocator);
+ defer t.report(@src());
+ const string = []const u8;
+ const cwd = "/Users/jarredsumner/Code/app";
+
+ _ = t.expect(
+ "/Users/jarredsumner/Code/app/foo/bar/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "foo", "bar", "file.js" }, .loose),
+ @src(),
+ );
+ _ = t.expect(
+ "/Users/jarredsumner/Code/app/foo/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "foo", "bar", "../file.js" }, .loose),
+ @src(),
+ );
+ _ = t.expect(
+ "/Users/jarredsumner/Code/app/foo/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "foo", "./bar", "../file.js" }, .loose),
+ @src(),
+ );
+
+ _ = t.expect(
+ "/Users/jarredsumner/Code/app/foo/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "././././foo", "././././bar././././", "../file.js" }, .loose),
+ @src(),
+ );
+
+ _ = t.expect(
+ "/Code/app/foo/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "/Code/app", "././././foo", "././././bar././././", "../file.js" }, .loose),
+ @src(),
+ );
+
+ _ = t.expect(
+ "/Code/app/foo/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "/Code/app", "././././foo", ".", "././././bar././././", ".", "../file.js" }, .loose),
+ @src(),
+ );
+
+ _ = t.expect(
+ "/Code/app/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "/Code/app", "././././foo", "..", "././././bar././././", ".", "../file.js" }, .loose),
+ @src(),
+ );
+
+ _ = t.expect(
+ "/Users/jarredsumner/Code/app/foo/bar/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "foo", "bar", "file.js" }, .loose),
+ @src(),
+ );
+ _ = t.expect(
+ "/Users/jarredsumner/Code/app/foo/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "foo", "bar", "../file.js" }, .loose),
+ @src(),
+ );
+ _ = t.expect(
+ "/Users/jarredsumner/Code/app/foo/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "foo", "./bar", "../file.js" }, .loose),
+ @src(),
+ );
+
+ _ = t.expect(
+ "/Users/jarredsumner/Code/app/foo/file.js",
+ normalizeAndJoinString(cwd, [_]string{ ".\\.\\.\\.\\foo", "././././bar././././", "..\\file.js" }, .loose),
+ @src(),
+ );
+
+ _ = t.expect(
+ "/Code/app/foo/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "/Code/app", "././././foo", "././././bar././././", "../file.js" }, .loose),
+ @src(),
+ );
+
+ _ = t.expect(
+ "/Code/app/foo/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "/Code/app", "././././foo", ".", "././././bar././././", ".", "../file.js" }, .loose),
+ @src(),
+ );
+
+ _ = t.expect(
+ "/Code/app/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "/Code/app", "././././foo", "..", "././././bar././././", ".", "../file.js" }, .loose),
+ @src(),
+ );
+}
- try testResolve("/", "..");
- try testResolve("/", "/..");
- try testResolve("/", "/../../../..");
- try testResolve("/a/b/c", "a/b/c/");
+test "normalizeStringPosix" {
+ var t = tester.Tester.t(std.heap.c_allocator);
+ defer t.report(@src());
- try testResolve("/new/date.txt", "/new/../../new/date.txt");
+ // Don't mess up strings that
+ _ = t.expect("foo/bar.txt", try normalizeStringAlloc(std.heap.c_allocator, "/foo/bar.txt", true, .posix), @src());
+ _ = t.expect("foo/bar.txt", try normalizeStringAlloc(std.heap.c_allocator, "/foo/bar.txt", false, .posix), @src());
+ _ = t.expect("foo/bar", try normalizeStringAlloc(std.heap.c_allocator, "/foo/bar", true, .posix), @src());
+ _ = t.expect("foo/bar", try normalizeStringAlloc(std.heap.c_allocator, "/foo/bar", false, .posix), @src());
+ _ = t.expect("foo/bar", try normalizeStringAlloc(std.heap.c_allocator, "/././foo/././././././bar/../bar/../bar", true, .posix), @src());
+ _ = t.expect("foo/bar", try normalizeStringAlloc(std.heap.c_allocator, "/foo/bar", false, .posix), @src());
+ _ = t.expect("foo/bar", try normalizeStringAlloc(std.heap.c_allocator, "/foo/bar//////", false, .posix), @src());
+ _ = t.expect("foo/bar", try normalizeStringAlloc(std.heap.c_allocator, "/////foo/bar//////", false, .posix), @src());
+ _ = t.expect("foo/bar", try normalizeStringAlloc(std.heap.c_allocator, "/////foo/bar", false, .posix), @src());
+ _ = t.expect("", try normalizeStringAlloc(std.heap.c_allocator, "/////", false, .posix), @src());
+ _ = t.expect("..", try normalizeStringAlloc(std.heap.c_allocator, "../boom/../", true, .posix), @src());
+ _ = t.expect("", try normalizeStringAlloc(std.heap.c_allocator, "./", true, .posix), @src());
}
-test "resolvePath overflow" {
- var buf: [1]u8 = undefined;
+test "normalizeStringWindows" {
+ var t = tester.Tester.t(std.heap.c_allocator);
+ defer t.report(@src());
- std.testing.expectEqualStrings("/", try resolvePath(&buf, "/"));
- std.testing.expectError(error.BufferTooSmall, resolvePath(&buf, "a")); // will resolve to "/a"
+ // Don't mess up strings that
+ _ = t.expect("foo\\bar.txt", try normalizeStringAlloc(std.heap.c_allocator, "\\foo\\bar.txt", true, .windows), @src());
+ _ = t.expect("foo\\bar.txt", try normalizeStringAlloc(std.heap.c_allocator, "\\foo\\bar.txt", false, .windows), @src());
+ _ = t.expect("foo\\bar", try normalizeStringAlloc(std.heap.c_allocator, "\\foo\\bar", true, .windows), @src());
+ _ = t.expect("foo\\bar", try normalizeStringAlloc(std.heap.c_allocator, "\\foo\\bar", false, .windows), @src());
+ _ = t.expect("foo\\bar", try normalizeStringAlloc(std.heap.c_allocator, "\\.\\.\\foo\\.\\.\\.\\.\\.\\.\\bar\\..\\bar\\..\\bar", true, .windows), @src());
+ _ = t.expect("foo\\bar", try normalizeStringAlloc(std.heap.c_allocator, "\\foo\\bar", false, .windows), @src());
+ _ = t.expect("foo\\bar", try normalizeStringAlloc(std.heap.c_allocator, "\\foo\\bar\\\\\\\\\\\\", false, .windows), @src());
+ _ = t.expect("foo\\bar", try normalizeStringAlloc(std.heap.c_allocator, "\\\\\\\\\\foo\\bar\\\\\\\\\\\\", false, .windows), @src());
+ _ = t.expect("foo\\bar", try normalizeStringAlloc(std.heap.c_allocator, "\\\\\\\\\\foo\\bar", false, .windows), @src());
+ _ = t.expect("", try normalizeStringAlloc(std.heap.c_allocator, "\\\\\\\\\\", false, .windows), @src());
+ _ = t.expect("..", try normalizeStringAlloc(std.heap.c_allocator, "..\\boom\\..\\", true, .windows), @src());
+ _ = t.expect("", try normalizeStringAlloc(std.heap.c_allocator, ".\\", true, .windows), @src());
}
diff --git a/src/resolver/resolver.zig b/src/resolver/resolver.zig
index 6304d032e..fd83656de 100644
--- a/src/resolver/resolver.zig
+++ b/src/resolver/resolver.zig
@@ -15,59 +15,7 @@ const hash_map_v2 = @import("../hash_map_v2.zig");
const Mutex = sync.Mutex;
const StringBoolMap = std.StringHashMap(bool);
-// https://en.wikipedia.org/wiki/.bss#BSS_in_C
-pub fn BSSSectionAllocator(comptime size: usize) type {
- const FixedBufferAllocator = std.heap.FixedBufferAllocator;
- return struct {
- var backing_buf: [size]u8 = undefined;
- var fixed_buffer_allocator = FixedBufferAllocator.init(&backing_buf);
- var buf_allocator = &fixed_buffer_allocator.allocator;
- const Allocator = std.mem.Allocator;
- const Self = @This();
-
- allocator: Allocator,
- fallback_allocator: *Allocator,
-
- pub fn get(self: *Self) *Allocator {
- return &self.allocator;
- }
-
- pub fn init(fallback_allocator: *Allocator) Self {
- return Self{ .fallback_allocator = fallback_allocator, .allocator = Allocator{
- .allocFn = BSSSectionAllocator(size).alloc,
- .resizeFn = BSSSectionAllocator(size).resize,
- } };
- }
-
- pub fn alloc(
- allocator: *Allocator,
- len: usize,
- ptr_align: u29,
- len_align: u29,
- return_address: usize,
- ) error{OutOfMemory}![]u8 {
- const self = @fieldParentPtr(Self, "allocator", allocator);
- return buf_allocator.allocFn(buf_allocator, len, ptr_align, len_align, return_address) catch
- return self.fallback_allocator.allocFn(self.fallback_allocator, len, ptr_align, len_align, return_address);
- }
-
- pub fn resize(
- allocator: *Allocator,
- buf: []u8,
- buf_align: u29,
- new_len: usize,
- len_align: u29,
- return_address: usize,
- ) error{OutOfMemory}!usize {
- const self = @fieldParentPtr(Self, "allocator", allocator);
- if (fixed_buffer_allocator.ownsPtr(buf.ptr)) {
- return fixed_buffer_allocator.allocator.resizeFn(&fixed_buffer_allocator.allocator, buf, buf_align, new_len, len_align, return_address);
- } else {
- return self.fallback_allocator.resizeFn(self.fallback_allocator, buf, buf_align, new_len, len_align, return_address);
- }
- }
- };
-}
+const allocators = @import("../allocators.zig");
const Path = Fs.Path;
@@ -84,11 +32,11 @@ pub const DirInfo = struct {
// These objects are immutable, so we can just point to the parent directory
// and avoid having to lock the cache again
- parent: Index = HashMap.NotFound,
+ parent: Index = allocators.NotFound,
// A pointer to the enclosing dirInfo with a valid "browser" field in
// package.json. We need this to remap paths after they have been resolved.
- enclosing_browser_scope: Index = HashMap.NotFound,
+ enclosing_browser_scope: Index = allocators.NotFound,
abs_path: string = "",
entries: Fs.FileSystem.DirEntry = undefined,
@@ -98,14 +46,10 @@ pub const DirInfo = struct {
abs_real_path: string = "", // If non-empty, this is the real absolute path resolving any symlinks
pub fn getParent(i: *DirInfo) ?*DirInfo {
- if (i.parent == HashMap.NotFound) return null;
- std.debug.assert(i.parent < HashMap.instance._data.len);
- return &HashMap.instance._data.items(.value)[i.parent];
+ return HashMap.instance.atIndex(i.parent);
}
pub fn getEnclosingBrowserScope(i: *DirInfo) ?*DirInfo {
- if (i.enclosing_browser_scope == HashMap.NotFound) return null;
- std.debug.assert(i.enclosing_browser_scope < HashMap.instance._data.len);
- return &HashMap.instance._data.items(.value)[i.enclosing_browser_scope];
+ return HashMap.instance.atIndex(i.enclosing_browser_scope);
}
// Goal: Really fast, low allocation directory map exploiting cache locality where we don't worry about lifetimes much.
@@ -113,98 +57,7 @@ pub const DirInfo = struct {
// 2. Don't expect a provided key to exist after it's queried
// 3. Store whether a directory has been queried and whether that query was successful.
// 4. Allocate onto the https://en.wikipedia.org/wiki/.bss#BSS_in_C instead of the heap, so we can avoid memory leaks
- pub const HashMap = struct {
- // In a small next.js app with few additional dependencies, there are 191 directories in the node_modules folder
- // fd . -d 9999 -L -t d --no-ignore | wc -l
- const PreallocatedCount = 256;
- const StringAllocatorSize = 128 * PreallocatedCount;
- const FallbackStringAllocator = BSSSectionAllocator(StringAllocatorSize);
- const FallbackAllocatorSize = @divExact(@bitSizeOf(Entry), 8) * PreallocatedCount;
- const FallbackAllocator = BSSSectionAllocator(FallbackAllocatorSize);
- const BackingHashMap = std.AutoHashMapUnmanaged(u64, Index);
- pub const Entry = struct {
- key: string,
- value: DirInfo,
- };
- string_allocator: FallbackStringAllocator,
- fallback_allocator: FallbackAllocator,
- allocator: *std.mem.Allocator,
- _data: std.MultiArrayList(Entry),
- hash_map: BackingHashMap,
- const Seed = 999;
- pub const NotFound: Index = std.math.maxInt(Index);
- var instance: HashMap = undefined;
-
- pub fn at(d: *HashMap, index: Index) *DirInfo {
- return &d._data.items(.value)[index];
- }
-
- pub const Result = struct {
- index: Index = NotFound,
- hash: u64 = 0,
- status: Status = Status.unknown,
-
- pub const Status = enum { unknown, not_found, exists };
- };
-
- // pub fn get(d: *HashMap, key: string) Result {
- // const _key = Wyhash.hash(Seed, key);
- // const index = d.hash_map.get(_key) orelse return Result{};
-
- // return d._data.items(.value)[index];
- // }
-
- pub fn getOrPut(
- d: *HashMap,
- key: string,
- ) Result {
- const _key = Wyhash.hash(Seed, key);
- const index = d.hash_map.get(_key) orelse return Result{
- .index = std.math.maxInt(u32),
- .status = .unknown,
- .hash = _key,
- };
- if (index == NotFound) {
- return Result{ .index = NotFound, .status = .not_found, .hash = _key };
- }
-
- return Result{ .index = index, .status = .exists, .hash = _key };
- }
-
- pub fn put(d: *HashMap, hash: u64, key: string, value: DirInfo) *DirInfo {
- const entry = Entry{
- .value = value,
- .key = d.string_allocator.get().dupe(u8, key) catch unreachable,
- };
- const index = d._data.len;
- d._data.append(d.fallback_allocator.get(), entry) catch unreachable;
- d.hash_map.put(d.allocator, hash, @intCast(DirInfo.Index, index)) catch unreachable;
- return &d._data.items(.value)[index];
- }
-
- pub fn init(allocator: *std.mem.Allocator) *HashMap {
- var list = std.MultiArrayList(Entry){};
- instance = HashMap{
- ._data = undefined,
- .string_allocator = FallbackStringAllocator.init(allocator),
- .allocator = allocator,
- .hash_map = BackingHashMap{},
- .fallback_allocator = FallbackAllocator.init(allocator),
- };
- list.ensureTotalCapacity(instance.allocator, PreallocatedCount) catch unreachable;
- instance._data = list;
- return &instance;
- }
-
- pub fn markNotFound(d: *HashMap, hash: u64) void {
- d.hash_map.put(d.allocator, hash, NotFound) catch unreachable;
- }
-
- pub fn deinit(i: *HashMap) void {
- i._data.deinit(i.allocator);
- i.hash_map.deinit(i.allocator);
- }
- };
+ pub const HashMap = allocators.BSSMap(DirInfo, 1024, true, 128);
};
pub const TemporaryBuffer = struct {
pub threadlocal var ExtensionPathBuf = std.mem.zeroes([512]u8);
@@ -578,7 +431,7 @@ pub const Resolver = struct {
if (check_relative) {
const parts = [_]string{ source_dir, import_path };
- const abs_path = std.fs.path.join(r.allocator, &parts) catch unreachable;
+ const abs_path = r.fs.join(&parts);
if (r.opts.external.abs_paths.count() > 0 and r.opts.external.abs_paths.exists(abs_path)) {
// If the string literal in the source text is an absolute path and has
@@ -760,7 +613,7 @@ pub const Resolver = struct {
// Try looking up the path relative to the base URL
if (tsconfig.base_url) |base| {
const paths = [_]string{ base, import_path };
- const abs = std.fs.path.join(r.allocator, &paths) catch unreachable;
+ const abs = r.fs.join(paths);
if (r.loadAsFileOrDirectory(abs, kind)) |res| {
return res;
@@ -775,7 +628,7 @@ pub const Resolver = struct {
// don't ever want to search for "node_modules/node_modules"
if (dir_info.has_node_modules) {
var _paths = [_]string{ dir_info.abs_path, "node_modules", import_path };
- const abs_path = std.fs.path.join(r.allocator, &_paths) catch unreachable;
+ const abs_path = r.fs.join(&_paths);
if (r.debug_logs) |*debug| {
debug.addNoteFmt("Checking for a package in the directory \"{s}\"", .{abs_path}) catch {};
}
@@ -802,7 +655,7 @@ pub const Resolver = struct {
return r.loadNodeModules(import_path, kind, source_dir_info);
} else {
const paths = [_]string{ source_dir_info.abs_path, import_path };
- var resolved = std.fs.path.join(r.allocator, &paths) catch unreachable;
+ var resolved = r.fs.join(&paths);
return r.loadAsFileOrDirectory(resolved, kind);
}
}
@@ -825,7 +678,7 @@ pub const Resolver = struct {
// // // Skip "node_modules" folders
// // if (!strings.eql(std.fs.path.basename(current), "node_modules")) {
// // var paths1 = [_]string{ current, "node_modules", extends };
- // // var join1 = std.fs.path.join(ctx.r.allocator, &paths1) catch unreachable;
+ // // var join1 = r.fs.joinAlloc(ctx.r.allocator, &paths1) catch unreachable;
// // const res = ctx.r.parseTSConfig(join1, ctx.visited) catch |err| {
// // if (err == error.ENOENT) {
// // continue;
@@ -857,14 +710,14 @@ pub const Resolver = struct {
// this might leak
if (!std.fs.path.isAbsolute(base)) {
const paths = [_]string{ file_dir, base };
- result.base_url = std.fs.path.join(r.allocator, &paths) catch unreachable;
+ result.base_url = r.fs.joinAlloc(r.allocator, &paths) catch unreachable;
}
}
if (result.paths.count() > 0 and (result.base_url_for_paths.len == 0 or !std.fs.path.isAbsolute(result.base_url_for_paths))) {
// this might leak
const paths = [_]string{ file_dir, result.base_url.? };
- result.base_url_for_paths = std.fs.path.join(r.allocator, &paths) catch unreachable;
+ result.base_url_for_paths = r.fs.joinAlloc(r.allocator, &paths) catch unreachable;
}
return result;
@@ -888,10 +741,13 @@ pub const Resolver = struct {
}
fn dirInfoCached(r: *Resolver, path: string) !?*DirInfo {
- var dir_info_entry = r.dir_cache.getOrPut(
- path,
- );
+ var dir_info_entry = try r.dir_cache.getOrPut(path);
+
+ var ptr = try r.dirInfoCachedGetOrPut(path, &dir_info_entry);
+ return ptr;
+ }
+ fn dirInfoCachedGetOrPut(r: *Resolver, path: string, dir_info_entry: *allocators.Result) !?*DirInfo {
switch (dir_info_entry.status) {
.unknown => {
return try r.dirInfoUncached(path, dir_info_entry);
@@ -900,7 +756,7 @@ pub const Resolver = struct {
return null;
},
.exists => {
- return r.dir_cache.at(dir_info_entry.index);
+ return r.dir_cache.atIndex(dir_info_entry.index);
},
}
// if (__entry.found_existing) {
@@ -953,7 +809,7 @@ pub const Resolver = struct {
if (!std.fs.path.isAbsolute(absolute_original_path)) {
const parts = [_]string{ abs_base_url, original_path };
- absolute_original_path = std.fs.path.join(r.allocator, &parts) catch unreachable;
+ absolute_original_path = r.fs.joinAlloc(r.allocator, &parts) catch unreachable;
was_alloc = true;
}
@@ -1032,12 +888,12 @@ pub const Resolver = struct {
const region = TemporaryBuffer.TSConfigMatchPathBuf[0..total_length];
// Load the original path relative to the "baseUrl" from tsconfig.json
- var absolute_original_path = region;
+ var absolute_original_path: string = region;
var did_allocate = false;
if (!std.fs.path.isAbsolute(region)) {
- const paths = [_]string{ abs_base_url, original_path };
- absolute_original_path = std.fs.path.join(r.allocator, &paths) catch unreachable;
+ var paths = [_]string{ abs_base_url, original_path };
+ absolute_original_path = r.fs.joinAlloc(r.allocator, &paths) catch unreachable;
did_allocate = true;
} else {
absolute_original_path = std.mem.dupe(r.allocator, u8, region) catch unreachable;
@@ -1059,7 +915,7 @@ pub const Resolver = struct {
pub fn checkBrowserMap(r: *Resolver, pkg: *PackageJSON, input_path: string) ?string {
// Normalize the path so we can compare against it without getting confused by "./"
- var cleaned = Path.normalizeNoAlloc(input_path, true);
+ var cleaned = r.fs.normalize(input_path);
const original_cleaned = cleaned;
if (cleaned.len == 1 and cleaned[0] == '.') {
@@ -1132,7 +988,7 @@ pub const Resolver = struct {
// Is the path disabled?
if (remap.len == 0) {
const paths = [_]string{ path, field_rel_path };
- const new_path = std.fs.path.join(r.allocator, &paths) catch unreachable;
+ const new_path = r.fs.joinAlloc(r.allocator, &paths) catch unreachable;
var _path = Path.init(new_path);
_path.is_disabled = true;
return MatchResult{
@@ -1147,7 +1003,7 @@ pub const Resolver = struct {
}
}
const _paths = [_]string{ field_rel_path, path };
- const field_abs_path = std.fs.path.join(r.allocator, &_paths) catch unreachable;
+ const field_abs_path = r.fs.joinAlloc(r.allocator, &_paths) catch unreachable;
const field_dir_info = (r.dirInfoCached(field_abs_path) catch null) orelse {
r.allocator.free(field_abs_path);
@@ -1171,7 +1027,7 @@ pub const Resolver = struct {
if (dir_info.entries.get(base)) |lookup| {
if (lookup.entry.kind(rfs) == .file) {
const parts = [_]string{ path, base };
- const out_buf = std.fs.path.join(r.allocator, &parts) catch unreachable;
+ const out_buf = r.fs.joinAlloc(r.allocator, &parts) catch unreachable;
if (r.debug_logs) |*debug| {
debug.addNoteFmt("Found file: \"{s}\"", .{out_buf}) catch unreachable;
}
@@ -1197,7 +1053,7 @@ pub const Resolver = struct {
// This doesn't really make sense to me.
if (remap.len == 0) {
const paths = [_]string{ path, field_rel_path };
- const new_path = std.fs.path.join(r.allocator, &paths) catch unreachable;
+ const new_path = r.fs.joinAlloc(r.allocator, &paths) catch unreachable;
var _path = Path.init(new_path);
_path.is_disabled = true;
return MatchResult{
@@ -1208,7 +1064,7 @@ pub const Resolver = struct {
}
const new_paths = [_]string{ path, remap };
- const remapped_abs = std.fs.path.join(r.allocator, &new_paths) catch unreachable;
+ const remapped_abs = r.fs.joinAlloc(r.allocator, &new_paths) catch unreachable;
// Is this a file
if (r.loadAsFile(remapped_abs, extension_order)) |file_result| {
@@ -1347,13 +1203,13 @@ pub const Resolver = struct {
}
}
- // Read the directory entries once to minimize locking
- const dir_path = std.fs.path.dirname(path) orelse unreachable; // Expected path to be a file.
- const dir_entry: Fs.FileSystem.RealFS.EntriesOption = r.fs.fs.readDirectory(dir_path) catch {
+ const dir_path = std.fs.path.dirname(path) orelse "/";
+
+ const dir_entry: *Fs.FileSystem.RealFS.EntriesOption = rfs.readDirectory(dir_path, null, false) catch {
return null;
};
- if (@as(Fs.FileSystem.RealFS.EntriesOption.Tag, dir_entry) == .err) {
+ if (@as(Fs.FileSystem.RealFS.EntriesOption.Tag, dir_entry.*) == .err) {
if (dir_entry.err.original_err != error.ENOENT) {
r.log.addErrorFmt(
null,
@@ -1383,8 +1239,9 @@ pub const Resolver = struct {
if (r.debug_logs) |*debug| {
debug.addNoteFmt("Found file \"{s}\" ", .{base}) catch {};
}
-
- return LoadResult{ .path = path, .diff_case = query.diff_case };
+ const abs_path_parts = [_]string{ query.entry.dir, query.entry.base };
+ const abs_path = r.fs.joinAlloc(r.allocator, &abs_path_parts) catch unreachable;
+ return LoadResult{ .path = abs_path, .diff_case = query.diff_case };
}
}
@@ -1467,74 +1324,96 @@ pub const Resolver = struct {
return null;
}
- fn dirInfoUncached(r: *Resolver, path: string, result: DirInfo.HashMap.Result) anyerror!?*DirInfo {
+ fn dirInfoUncached(r: *Resolver, unsafe_path: string, result: *allocators.Result) anyerror!?*DirInfo {
var rfs: *Fs.FileSystem.RealFS = &r.fs.fs;
var parent: ?*DirInfo = null;
- const parent_dir = std.fs.path.dirname(path) orelse {
- r.dir_cache.markNotFound(result.hash);
- return null;
- };
+ var is_root = false;
+ const parent_dir = (std.fs.path.dirname(unsafe_path) orelse parent_dir_handle: {
+ is_root = true;
+ break :parent_dir_handle "/";
+ });
- var parent_result: DirInfo.HashMap.Result = undefined;
- if (parent_dir.len > 1 and !strings.eql(parent_dir, path)) {
- parent = (try r.dirInfoCached(parent_dir)) orelse {
- r.dir_cache.markNotFound(result.hash);
- return null;
- };
+ var parent_result: allocators.Result = allocators.Result{
+ .hash = std.math.maxInt(u64),
+ .index = allocators.NotFound,
+ .status = .unknown,
+ };
+ if (!is_root and !strings.eql(parent_dir, unsafe_path)) {
+ parent = r.dirInfoCached(parent_dir) catch null;
- parent_result = r.dir_cache.getOrPut(parent_dir);
+ if (parent != null) {
+ parent_result = try r.dir_cache.getOrPut(parent_dir);
+ }
}
+ var entries: Fs.FileSystem.DirEntry = Fs.FileSystem.DirEntry.empty(unsafe_path, r.allocator);
+
// List the directories
- var _entries = try rfs.readDirectory(path);
- var entries: @TypeOf(_entries.entries) = undefined;
- if (std.meta.activeTag(_entries) == .err) {
- // Just pretend this directory is empty if we can't access it. This is the
- // case on Unix for directories that only have the execute permission bit
- // set. It means we will just pass through the empty directory and
- // continue to check the directories above it, which is now node behaves.
- switch (_entries.err.original_err) {
- error.EACCESS => {
- entries = Fs.FileSystem.DirEntry.empty(path, r.allocator);
- },
+ if (!is_root) {
+ var _entries: *Fs.FileSystem.RealFS.EntriesOption = undefined;
+
+ _entries = try rfs.readDirectory(unsafe_path, null, true);
+
+ if (std.meta.activeTag(_entries.*) == .err) {
+ // Just pretend this directory is empty if we can't access it. This is the
+ // case on Unix for directories that only have the execute permission bit
+ // set. It means we will just pass through the empty directory and
+ // continue to check the directories above it, which is now node behaves.
+ switch (_entries.err.original_err) {
+ error.EACCESS => {
+ entries = Fs.FileSystem.DirEntry.empty(unsafe_path, r.allocator);
+ },
- // Ignore "ENOTDIR" here so that calling "ReadDirectory" on a file behaves
- // as if there is nothing there at all instead of causing an error due to
- // the directory actually being a file. This is a workaround for situations
- // where people try to import from a path containing a file as a parent
- // directory. The "pnpm" package manager generates a faulty "NODE_PATH"
- // list which contains such paths and treating them as missing means we just
- // ignore them during path resolution.
- error.ENOENT,
- error.ENOTDIR,
- => {},
- else => {
- const pretty = r.prettyPath(Path.init(path));
- r.log.addErrorFmt(
- null,
- logger.Loc{},
- r.allocator,
- "Cannot read directory \"{s}\": {s}",
- .{
- pretty,
- @errorName(_entries.err.original_err),
- },
- ) catch {};
- r.dir_cache.markNotFound(result.hash);
- return null;
- },
+ // Ignore "ENOTDIR" here so that calling "ReadDirectory" on a file behaves
+ // as if there is nothing there at all instead of causing an error due to
+ // the directory actually being a file. This is a workaround for situations
+ // where people try to import from a path containing a file as a parent
+ // directory. The "pnpm" package manager generates a faulty "NODE_PATH"
+ // list which contains such paths and treating them as missing means we just
+ // ignore them during path resolution.
+ error.ENOENT,
+ error.ENOTDIR,
+ error.IsDir,
+ => {
+ entries = Fs.FileSystem.DirEntry.empty(unsafe_path, r.allocator);
+ },
+ else => {
+ const pretty = r.prettyPath(Path.init(unsafe_path));
+ result.status = .not_found;
+ r.log.addErrorFmt(
+ null,
+ logger.Loc{},
+ r.allocator,
+ "Cannot read directory \"{s}\": {s}",
+ .{
+ pretty,
+ @errorName(_entries.err.original_err),
+ },
+ ) catch {};
+ r.dir_cache.markNotFound(result.*);
+ return null;
+ },
+ }
+ } else {
+ entries = _entries.entries;
}
- } else {
- entries = _entries.entries;
}
- var info = DirInfo{
- .abs_path = path,
- .parent = if (parent != null) parent_result.index else DirInfo.HashMap.NotFound,
- .entries = entries,
+ var info = dir_info_getter: {
+ var _info = DirInfo{
+ .abs_path = "",
+ .parent = parent_result.index,
+ .entries = entries,
+ };
+ result.status = .exists;
+ var __info = try r.dir_cache.put(unsafe_path, true, result, _info);
+ __info.abs_path = r.dir_cache.keyAtIndex(result.index).?;
+ break :dir_info_getter __info;
};
+ const path = info.abs_path;
+
// A "node_modules" directory isn't allowed to directly contain another "node_modules" directory
var base = std.fs.path.basename(path);
if (!strings.eqlComptime(base, "node_modules")) {
@@ -1544,29 +1423,31 @@ pub const Resolver = struct {
}
}
- // Propagate the browser scope into child directories
- if (parent) |parent_info| {
- info.enclosing_browser_scope = parent_info.enclosing_browser_scope;
+ if (parent_result.status != .unknown) {
+ // Propagate the browser scope into child directories
+ if (parent) |parent_info| {
+ info.enclosing_browser_scope = parent_info.enclosing_browser_scope;
- // Make sure "absRealPath" is the real path of the directory (resolving any symlinks)
- if (!r.opts.preserve_symlinks) {
- if (parent_info.entries.get(base)) |lookup| {
- const entry = lookup.entry;
+ // Make sure "absRealPath" is the real path of the directory (resolving any symlinks)
+ if (!r.opts.preserve_symlinks) {
+ if (parent_info.entries.get(base)) |lookup| {
+ const entry = lookup.entry;
- var symlink = entry.symlink(rfs);
- if (symlink.len > 0) {
- if (r.debug_logs) |*logs| {
- try logs.addNote(std.fmt.allocPrint(r.allocator, "Resolved symlink \"{s}\" to \"{s}\"", .{ path, symlink }) catch unreachable);
- }
- info.abs_real_path = symlink;
- } else if (parent_info.abs_real_path.len > 0) {
- // this might leak a little i'm not sure
- const parts = [_]string{ parent_info.abs_real_path, base };
- symlink = std.fs.path.join(r.allocator, &parts) catch unreachable;
- if (r.debug_logs) |*logs| {
- try logs.addNote(std.fmt.allocPrint(r.allocator, "Resolved symlink \"{s}\" to \"{s}\"", .{ path, symlink }) catch unreachable);
+ var symlink = entry.symlink(rfs);
+ if (symlink.len > 0) {
+ if (r.debug_logs) |*logs| {
+ try logs.addNote(std.fmt.allocPrint(r.allocator, "Resolved symlink \"{s}\" to \"{s}\"", .{ path, symlink }) catch unreachable);
+ }
+ info.abs_real_path = symlink;
+ } else if (parent_info.abs_real_path.len > 0) {
+ // this might leak a little i'm not sure
+ const parts = [_]string{ parent_info.abs_real_path, base };
+ symlink = r.fs.joinAlloc(r.allocator, &parts) catch unreachable;
+ if (r.debug_logs) |*logs| {
+ try logs.addNote(std.fmt.allocPrint(r.allocator, "Resolved symlink \"{s}\" to \"{s}\"", .{ path, symlink }) catch unreachable);
+ }
+ info.abs_real_path = symlink;
}
- info.abs_real_path = symlink;
}
}
}
@@ -1580,8 +1461,7 @@ pub const Resolver = struct {
if (info.package_json) |pkg| {
if (pkg.browser_map.count() > 0) {
- // it has not been written yet, so we reserve the next index
- info.enclosing_browser_scope = @intCast(DirInfo.Index, DirInfo.HashMap.instance._data.len);
+ info.enclosing_browser_scope = result.index;
}
if (r.debug_logs) |*logs| {
@@ -1601,7 +1481,7 @@ pub const Resolver = struct {
const entry = lookup.entry;
if (entry.kind(rfs) == .file) {
const parts = [_]string{ path, "tsconfig.json" };
- tsconfig_path = try std.fs.path.join(r.allocator, &parts);
+ tsconfig_path = try r.fs.joinAlloc(r.allocator, &parts);
}
}
if (tsconfig_path == null) {
@@ -1609,7 +1489,7 @@ pub const Resolver = struct {
const entry = lookup.entry;
if (entry.kind(rfs) == .file) {
const parts = [_]string{ path, "jsconfig.json" };
- tsconfig_path = try std.fs.path.join(r.allocator, &parts);
+ tsconfig_path = try r.fs.joinAlloc(r.allocator, &parts);
}
}
}
@@ -1625,7 +1505,7 @@ pub const Resolver = struct {
if (err == error.ENOENT) {
r.log.addErrorFmt(null, logger.Loc.Empty, r.allocator, "Cannot find tsconfig file \"{s}\"", .{pretty}) catch unreachable;
- } else if (err != error.ParseErrorAlreadyLogged) {
+ } else if (err != error.ParseErrorAlreadyLogged and err != error.IsDir) {
r.log.addErrorFmt(null, logger.Loc.Empty, r.allocator, "Cannot read file \"{s}\": {s}", .{ pretty, @errorName(err) }) catch unreachable;
}
break :brk null;
@@ -1637,7 +1517,6 @@ pub const Resolver = struct {
info.tsconfig_json = parent.?.tsconfig_json;
}
- info.entries = entries;
- return r.dir_cache.put(result.hash, path, info);
+ return info;
}
};
diff --git a/src/test/fixtures/exports-bug.js b/src/test/fixtures/exports-bug.js
new file mode 100644
index 000000000..081b8cfa0
--- /dev/null
+++ b/src/test/fixtures/exports-bug.js
@@ -0,0 +1,2342 @@
+import {
+ RGBAFormat,
+ HalfFloatType,
+ FloatType,
+ UnsignedByteType,
+ TriangleFanDrawMode,
+ TriangleStripDrawMode,
+ TrianglesDrawMode,
+ LinearToneMapping,
+ BackSide,
+} from "../constants.js";
+import { _Math } from "../math/Math.js";
+import { DataTexture } from "../textures/DataTexture.js";
+import { Frustum } from "../math/Frustum.js";
+import { Matrix4 } from "../math/Matrix4.js";
+import { ShaderLib } from "./shaders/ShaderLib.js";
+import { UniformsLib } from "./shaders/UniformsLib.js";
+import { cloneUniforms } from "./shaders/UniformsUtils.js";
+import { Vector2 } from "../math/Vector2.js";
+import { Vector3 } from "../math/Vector3.js";
+import { Vector4 } from "../math/Vector4.js";
+import { WebGLAnimation } from "./webgl/WebGLAnimation.js";
+import { WebGLAttributes } from "./webgl/WebGLAttributes.js";
+import { WebGLBackground } from "./webgl/WebGLBackground.js";
+import { WebGLBufferRenderer } from "./webgl/WebGLBufferRenderer.js";
+import { WebGLCapabilities } from "./webgl/WebGLCapabilities.js";
+import { WebGLClipping } from "./webgl/WebGLClipping.js";
+import { WebGLExtensions } from "./webgl/WebGLExtensions.js";
+import { WebGLGeometries } from "./webgl/WebGLGeometries.js";
+import { WebGLIndexedBufferRenderer } from "./webgl/WebGLIndexedBufferRenderer.js";
+import { WebGLInfo } from "./webgl/WebGLInfo.js";
+import { WebGLMorphtargets } from "./webgl/WebGLMorphtargets.js";
+import { WebGLObjects } from "./webgl/WebGLObjects.js";
+import { WebGLPrograms } from "./webgl/WebGLPrograms.js";
+import { WebGLProperties } from "./webgl/WebGLProperties.js";
+import { WebGLRenderLists } from "./webgl/WebGLRenderLists.js";
+import { WebGLRenderStates } from "./webgl/WebGLRenderStates.js";
+import { WebGLShadowMap } from "./webgl/WebGLShadowMap.js";
+import { WebGLState } from "./webgl/WebGLState.js";
+import { WebGLTextures } from "./webgl/WebGLTextures.js";
+import { WebGLUniforms } from "./webgl/WebGLUniforms.js";
+import { WebGLUtils } from "./webgl/WebGLUtils.js";
+import { WebVRManager } from "./webvr/WebVRManager.js";
+import { WebXRManager } from "./webvr/WebXRManager.js";
+
+/**
+ * @author supereggbert / http://www.paulbrunt.co.uk/
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ * @author szimek / https://github.com/szimek/
+ * @author tschw
+ */
+
+function WebGLRenderer(parameters) {
+ parameters = parameters || {};
+
+ var _canvas =
+ parameters.canvas !== undefined
+ ? parameters.canvas
+ : document.createElementNS("http://www.w3.org/1999/xhtml", "canvas"),
+ _context = parameters.context !== undefined ? parameters.context : null,
+ _alpha = parameters.alpha !== undefined ? parameters.alpha : false,
+ _depth = parameters.depth !== undefined ? parameters.depth : true,
+ _stencil = parameters.stencil !== undefined ? parameters.stencil : true,
+ _antialias =
+ parameters.antialias !== undefined ? parameters.antialias : false,
+ _premultipliedAlpha =
+ parameters.premultipliedAlpha !== undefined
+ ? parameters.premultipliedAlpha
+ : true,
+ _preserveDrawingBuffer =
+ parameters.preserveDrawingBuffer !== undefined
+ ? parameters.preserveDrawingBuffer
+ : false,
+ _powerPreference =
+ parameters.powerPreference !== undefined
+ ? parameters.powerPreference
+ : "default",
+ _failIfMajorPerformanceCaveat =
+ parameters.failIfMajorPerformanceCaveat !== undefined
+ ? parameters.failIfMajorPerformanceCaveat
+ : false;
+
+ var currentRenderList = null;
+ var currentRenderState = null;
+
+ // public properties
+
+ this.domElement = _canvas;
+
+ // Debug configuration container
+ this.debug = {
+ /**
+ * Enables error checking and reporting when shader programs are being compiled
+ * @type {boolean}
+ */
+ checkShaderErrors: true,
+ };
+
+ // clearing
+
+ this.autoClear = true;
+ this.autoClearColor = true;
+ this.autoClearDepth = true;
+ this.autoClearStencil = true;
+
+ // scene graph
+
+ this.sortObjects = true;
+
+ // user-defined clipping
+
+ this.clippingPlanes = [];
+ this.localClippingEnabled = false;
+
+ // physically based shading
+
+ this.gammaFactor = 2.0; // for backwards compatibility
+ this.gammaInput = false;
+ this.gammaOutput = false;
+
+ // physical lights
+
+ this.physicallyCorrectLights = false;
+
+ // tone mapping
+
+ this.toneMapping = LinearToneMapping;
+ this.toneMappingExposure = 1.0;
+ this.toneMappingWhitePoint = 1.0;
+
+ // morphs
+
+ this.maxMorphTargets = 8;
+ this.maxMorphNormals = 4;
+
+ // internal properties
+
+ var _this = this,
+ _isContextLost = false,
+ // internal state cache
+
+ _framebuffer = null,
+ _currentActiveCubeFace = 0,
+ _currentActiveMipmapLevel = 0,
+ _currentRenderTarget = null,
+ _currentFramebuffer = null,
+ _currentMaterialId = -1,
+ // geometry and program caching
+
+ _currentGeometryProgram = {
+ geometry: null,
+ program: null,
+ wireframe: false,
+ },
+ _currentCamera = null,
+ _currentArrayCamera = null,
+ _currentViewport = new Vector4(),
+ _currentScissor = new Vector4(),
+ _currentScissorTest = null,
+ //
+
+ _width = _canvas.width,
+ _height = _canvas.height,
+ _pixelRatio = 1,
+ _viewport = new Vector4(0, 0, _width, _height),
+ _scissor = new Vector4(0, 0, _width, _height),
+ _scissorTest = false,
+ // frustum
+
+ _frustum = new Frustum(),
+ // clipping
+
+ _clipping = new WebGLClipping(),
+ _clippingEnabled = false,
+ _localClippingEnabled = false,
+ // camera matrices cache
+
+ _projScreenMatrix = new Matrix4(),
+ _vector3 = new Vector3();
+
+ function getTargetPixelRatio() {
+ return _currentRenderTarget === null ? _pixelRatio : 1;
+ }
+
+ // initialize
+
+ var _gl;
+
+ try {
+ var contextAttributes = {
+ alpha: _alpha,
+ depth: _depth,
+ stencil: _stencil,
+ antialias: _antialias,
+ premultipliedAlpha: _premultipliedAlpha,
+ preserveDrawingBuffer: _preserveDrawingBuffer,
+ powerPreference: _powerPreference,
+ failIfMajorPerformanceCaveat: _failIfMajorPerformanceCaveat,
+ xrCompatible: true,
+ };
+
+ // event listeners must be registered before WebGL context is created, see #12753
+
+ _canvas.addEventListener("webglcontextlost", onContextLost, false);
+ _canvas.addEventListener("webglcontextrestored", onContextRestore, false);
+
+ _gl =
+ _context ||
+ _canvas.getContext("webgl", contextAttributes) ||
+ _canvas.getContext("experimental-webgl", contextAttributes);
+
+ if (_gl === null) {
+ if (_canvas.getContext("webgl") !== null) {
+ throw new Error(
+ "Error creating WebGL context with your selected attributes."
+ );
+ } else {
+ throw new Error("Error creating WebGL context.");
+ }
+ }
+
+ // Some experimental-webgl implementations do not have getShaderPrecisionFormat
+
+ if (_gl.getShaderPrecisionFormat === undefined) {
+ _gl.getShaderPrecisionFormat = function () {
+ return { rangeMin: 1, rangeMax: 1, precision: 1 };
+ };
+ }
+ } catch (error) {
+ console.error("THREE.WebGLRenderer: " + error.message);
+ throw error;
+ }
+
+ var extensions, capabilities, state, info;
+ var properties, textures, attributes, geometries, objects;
+ var programCache, renderLists, renderStates;
+
+ var background, morphtargets, bufferRenderer, indexedBufferRenderer;
+
+ var utils;
+
+ function initGLContext() {
+ extensions = new WebGLExtensions(_gl);
+
+ capabilities = new WebGLCapabilities(_gl, extensions, parameters);
+
+ if (!capabilities.isWebGL2) {
+ extensions.get("WEBGL_depth_texture");
+ extensions.get("OES_texture_float");
+ extensions.get("OES_texture_half_float");
+ extensions.get("OES_texture_half_float_linear");
+ extensions.get("OES_standard_derivatives");
+ extensions.get("OES_element_index_uint");
+ extensions.get("ANGLE_instanced_arrays");
+ }
+
+ extensions.get("OES_texture_float_linear");
+
+ utils = new WebGLUtils(_gl, extensions, capabilities);
+
+ state = new WebGLState(_gl, extensions, utils, capabilities);
+ state.scissor(
+ _currentScissor.copy(_scissor).multiplyScalar(_pixelRatio).floor()
+ );
+ state.viewport(
+ _currentViewport.copy(_viewport).multiplyScalar(_pixelRatio).floor()
+ );
+
+ info = new WebGLInfo(_gl);
+ properties = new WebGLProperties();
+ textures = new WebGLTextures(
+ _gl,
+ extensions,
+ state,
+ properties,
+ capabilities,
+ utils,
+ info
+ );
+ attributes = new WebGLAttributes(_gl);
+ geometries = new WebGLGeometries(_gl, attributes, info);
+ objects = new WebGLObjects(geometries, info);
+ morphtargets = new WebGLMorphtargets(_gl);
+ programCache = new WebGLPrograms(_this, extensions, capabilities);
+ renderLists = new WebGLRenderLists();
+ renderStates = new WebGLRenderStates();
+
+ background = new WebGLBackground(
+ _this,
+ state,
+ objects,
+ _premultipliedAlpha
+ );
+
+ bufferRenderer = new WebGLBufferRenderer(
+ _gl,
+ extensions,
+ info,
+ capabilities
+ );
+ indexedBufferRenderer = new WebGLIndexedBufferRenderer(
+ _gl,
+ extensions,
+ info,
+ capabilities
+ );
+
+ info.programs = programCache.programs;
+
+ _this.capabilities = capabilities;
+ _this.extensions = extensions;
+ _this.properties = properties;
+ _this.renderLists = renderLists;
+ _this.state = state;
+ _this.info = info;
+ }
+
+ initGLContext();
+
+ // vr
+
+ var vr =
+ typeof navigator !== "undefined" &&
+ "xr" in navigator &&
+ "supportsSession" in navigator.xr
+ ? new WebXRManager(_this, _gl)
+ : new WebVRManager(_this);
+
+ this.vr = vr;
+
+ // shadow map
+
+ var shadowMap = new WebGLShadowMap(
+ _this,
+ objects,
+ capabilities.maxTextureSize
+ );
+
+ this.shadowMap = shadowMap;
+
+ // API
+
+ this.getContext = function () {
+ return _gl;
+ };
+
+ this.getContextAttributes = function () {
+ return _gl.getContextAttributes();
+ };
+
+ this.forceContextLoss = function () {
+ var extension = extensions.get("WEBGL_lose_context");
+ if (extension) extension.loseContext();
+ };
+
+ this.forceContextRestore = function () {
+ var extension = extensions.get("WEBGL_lose_context");
+ if (extension) extension.restoreContext();
+ };
+
+ this.getPixelRatio = function () {
+ return _pixelRatio;
+ };
+
+ this.setPixelRatio = function (value) {
+ if (value === undefined) return;
+
+ _pixelRatio = value;
+
+ this.setSize(_width, _height, false);
+ };
+
+ this.getSize = function (target) {
+ if (target === undefined) {
+ console.warn(
+ "WebGLRenderer: .getsize() now requires a Vector2 as an argument"
+ );
+
+ target = new Vector2();
+ }
+
+ return target.set(_width, _height);
+ };
+
+ this.setSize = function (width, height, updateStyle) {
+ if (vr.isPresenting()) {
+ console.warn(
+ "THREE.WebGLRenderer: Can't change size while VR device is presenting."
+ );
+ return;
+ }
+
+ _width = width;
+ _height = height;
+
+ _canvas.width = Math.floor(width * _pixelRatio);
+ _canvas.height = Math.floor(height * _pixelRatio);
+
+ if (updateStyle !== false) {
+ _canvas.style.width = width + "px";
+ _canvas.style.height = height + "px";
+ }
+
+ this.setViewport(0, 0, width, height);
+ };
+
+ this.getDrawingBufferSize = function (target) {
+ if (target === undefined) {
+ console.warn(
+ "WebGLRenderer: .getdrawingBufferSize() now requires a Vector2 as an argument"
+ );
+
+ target = new Vector2();
+ }
+
+ return target.set(_width * _pixelRatio, _height * _pixelRatio).floor();
+ };
+
+ this.setDrawingBufferSize = function (width, height, pixelRatio) {
+ _width = width;
+ _height = height;
+
+ _pixelRatio = pixelRatio;
+
+ _canvas.width = Math.floor(width * pixelRatio);
+ _canvas.height = Math.floor(height * pixelRatio);
+
+ this.setViewport(0, 0, width, height);
+ };
+
+ this.getCurrentViewport = function (target) {
+ if (target === undefined) {
+ console.warn(
+ "WebGLRenderer: .getCurrentViewport() now requires a Vector4 as an argument"
+ );
+
+ target = new Vector4();
+ }
+
+ return target.copy(_currentViewport);
+ };
+
+ this.getViewport = function (target) {
+ return target.copy(_viewport);
+ };
+
+ this.setViewport = function (x, y, width, height) {
+ if (x.isVector4) {
+ _viewport.set(x.x, x.y, x.z, x.w);
+ } else {
+ _viewport.set(x, y, width, height);
+ }
+
+ state.viewport(
+ _currentViewport.copy(_viewport).multiplyScalar(_pixelRatio).floor()
+ );
+ };
+
+ this.getScissor = function (target) {
+ return target.copy(_scissor);
+ };
+
+ this.setScissor = function (x, y, width, height) {
+ if (x.isVector4) {
+ _scissor.set(x.x, x.y, x.z, x.w);
+ } else {
+ _scissor.set(x, y, width, height);
+ }
+
+ state.scissor(
+ _currentScissor.copy(_scissor).multiplyScalar(_pixelRatio).floor()
+ );
+ };
+
+ this.getScissorTest = function () {
+ return _scissorTest;
+ };
+
+ this.setScissorTest = function (boolean) {
+ state.setScissorTest((_scissorTest = boolean));
+ };
+
+ // Clearing
+
+ this.getClearColor = function () {
+ return background.getClearColor();
+ };
+
+ this.setClearColor = function () {
+ background.setClearColor.apply(background, arguments);
+ };
+
+ this.getClearAlpha = function () {
+ return background.getClearAlpha();
+ };
+
+ this.setClearAlpha = function () {
+ background.setClearAlpha.apply(background, arguments);
+ };
+
+ this.clear = function (color, depth, stencil) {
+ var bits = 0;
+
+ if (color === undefined || color) bits |= _gl.COLOR_BUFFER_BIT;
+ if (depth === undefined || depth) bits |= _gl.DEPTH_BUFFER_BIT;
+ if (stencil === undefined || stencil) bits |= _gl.STENCIL_BUFFER_BIT;
+
+ _gl.clear(bits);
+ };
+
+ this.clearColor = function () {
+ this.clear(true, false, false);
+ };
+
+ this.clearDepth = function () {
+ this.clear(false, true, false);
+ };
+
+ this.clearStencil = function () {
+ this.clear(false, false, true);
+ };
+
+ //
+
+ this.dispose = function () {
+ _canvas.removeEventListener("webglcontextlost", onContextLost, false);
+ _canvas.removeEventListener(
+ "webglcontextrestored",
+ onContextRestore,
+ false
+ );
+
+ renderLists.dispose();
+ renderStates.dispose();
+ properties.dispose();
+ objects.dispose();
+
+ vr.dispose();
+
+ animation.stop();
+ };
+
+ // Events
+
+ function onContextLost(event) {
+ event.preventDefault();
+
+ console.log("THREE.WebGLRenderer: Context Lost.");
+
+ _isContextLost = true;
+ }
+
+ function onContextRestore(/* event */) {
+ console.log("THREE.WebGLRenderer: Context Restored.");
+
+ _isContextLost = false;
+
+ initGLContext();
+ }
+
+ function onMaterialDispose(event) {
+ var material = event.target;
+
+ material.removeEventListener("dispose", onMaterialDispose);
+
+ deallocateMaterial(material);
+ }
+
+ // Buffer deallocation
+
+ function deallocateMaterial(material) {
+ releaseMaterialProgramReference(material);
+
+ properties.remove(material);
+ }
+
+ function releaseMaterialProgramReference(material) {
+ var programInfo = properties.get(material).program;
+
+ material.program = undefined;
+
+ if (programInfo !== undefined) {
+ programCache.releaseProgram(programInfo);
+ }
+ }
+
+ // Buffer rendering
+
+ function renderObjectImmediate(object, program) {
+ object.render(function (object) {
+ _this.renderBufferImmediate(object, program);
+ });
+ }
+
+ this.renderBufferImmediate = function (object, program) {
+ state.initAttributes();
+
+ var buffers = properties.get(object);
+
+ if (object.hasPositions && !buffers.position)
+ buffers.position = _gl.createBuffer();
+ if (object.hasNormals && !buffers.normal)
+ buffers.normal = _gl.createBuffer();
+ if (object.hasUvs && !buffers.uv) buffers.uv = _gl.createBuffer();
+ if (object.hasColors && !buffers.color) buffers.color = _gl.createBuffer();
+
+ var programAttributes = program.getAttributes();
+
+ if (object.hasPositions) {
+ _gl.bindBuffer(_gl.ARRAY_BUFFER, buffers.position);
+ _gl.bufferData(_gl.ARRAY_BUFFER, object.positionArray, _gl.DYNAMIC_DRAW);
+
+ state.enableAttribute(programAttributes.position);
+ _gl.vertexAttribPointer(
+ programAttributes.position,
+ 3,
+ _gl.FLOAT,
+ false,
+ 0,
+ 0
+ );
+ }
+
+ if (object.hasNormals) {
+ _gl.bindBuffer(_gl.ARRAY_BUFFER, buffers.normal);
+ _gl.bufferData(_gl.ARRAY_BUFFER, object.normalArray, _gl.DYNAMIC_DRAW);
+
+ state.enableAttribute(programAttributes.normal);
+ _gl.vertexAttribPointer(
+ programAttributes.normal,
+ 3,
+ _gl.FLOAT,
+ false,
+ 0,
+ 0
+ );
+ }
+
+ if (object.hasUvs) {
+ _gl.bindBuffer(_gl.ARRAY_BUFFER, buffers.uv);
+ _gl.bufferData(_gl.ARRAY_BUFFER, object.uvArray, _gl.DYNAMIC_DRAW);
+
+ state.enableAttribute(programAttributes.uv);
+ _gl.vertexAttribPointer(programAttributes.uv, 2, _gl.FLOAT, false, 0, 0);
+ }
+
+ if (object.hasColors) {
+ _gl.bindBuffer(_gl.ARRAY_BUFFER, buffers.color);
+ _gl.bufferData(_gl.ARRAY_BUFFER, object.colorArray, _gl.DYNAMIC_DRAW);
+
+ state.enableAttribute(programAttributes.color);
+ _gl.vertexAttribPointer(
+ programAttributes.color,
+ 3,
+ _gl.FLOAT,
+ false,
+ 0,
+ 0
+ );
+ }
+
+ state.disableUnusedAttributes();
+
+ _gl.drawArrays(_gl.TRIANGLES, 0, object.count);
+
+ object.count = 0;
+ };
+
+ this.renderBufferDirect = function (
+ camera,
+ fog,
+ geometry,
+ material,
+ object,
+ group
+ ) {
+ var frontFaceCW = object.isMesh && object.matrixWorld.determinant() < 0;
+
+ state.setMaterial(material, frontFaceCW);
+
+ var program = setProgram(camera, fog, material, object);
+
+ var updateBuffers = false;
+
+ if (
+ _currentGeometryProgram.geometry !== geometry.id ||
+ _currentGeometryProgram.program !== program.id ||
+ _currentGeometryProgram.wireframe !== (material.wireframe === true)
+ ) {
+ _currentGeometryProgram.geometry = geometry.id;
+ _currentGeometryProgram.program = program.id;
+ _currentGeometryProgram.wireframe = material.wireframe === true;
+ updateBuffers = true;
+ }
+
+ if (object.morphTargetInfluences) {
+ morphtargets.update(object, geometry, material, program);
+
+ updateBuffers = true;
+ }
+
+ //
+
+ var index = geometry.index;
+ var position = geometry.attributes.position;
+ var rangeFactor = 1;
+
+ if (material.wireframe === true) {
+ index = geometries.getWireframeAttribute(geometry);
+ rangeFactor = 2;
+ }
+
+ var attribute;
+ var renderer = bufferRenderer;
+
+ if (index !== null) {
+ attribute = attributes.get(index);
+
+ renderer = indexedBufferRenderer;
+ renderer.setIndex(attribute);
+ }
+
+ if (updateBuffers) {
+ setupVertexAttributes(material, program, geometry);
+
+ if (index !== null) {
+ _gl.bindBuffer(_gl.ELEMENT_ARRAY_BUFFER, attribute.buffer);
+ }
+ }
+
+ //
+
+ var dataCount = Infinity;
+
+ if (index !== null) {
+ dataCount = index.count;
+ } else if (position !== undefined) {
+ dataCount = position.count;
+ }
+
+ var rangeStart = geometry.drawRange.start * rangeFactor;
+ var rangeCount = geometry.drawRange.count * rangeFactor;
+
+ var groupStart = group !== null ? group.start * rangeFactor : 0;
+ var groupCount = group !== null ? group.count * rangeFactor : Infinity;
+
+ var drawStart = Math.max(rangeStart, groupStart);
+ var drawEnd =
+ Math.min(dataCount, rangeStart + rangeCount, groupStart + groupCount) - 1;
+
+ var drawCount = Math.max(0, drawEnd - drawStart + 1);
+
+ if (drawCount === 0) return;
+
+ //
+
+ if (object.isMesh) {
+ if (material.wireframe === true) {
+ state.setLineWidth(material.wireframeLinewidth * getTargetPixelRatio());
+ renderer.setMode(_gl.LINES);
+ } else {
+ switch (object.drawMode) {
+ case TrianglesDrawMode:
+ renderer.setMode(_gl.TRIANGLES);
+ break;
+
+ case TriangleStripDrawMode:
+ renderer.setMode(_gl.TRIANGLE_STRIP);
+ break;
+
+ case TriangleFanDrawMode:
+ renderer.setMode(_gl.TRIANGLE_FAN);
+ break;
+ }
+ }
+ } else if (object.isLine) {
+ var lineWidth = material.linewidth;
+
+ if (lineWidth === undefined) lineWidth = 1; // Not using Line*Material
+
+ state.setLineWidth(lineWidth * getTargetPixelRatio());
+
+ if (object.isLineSegments) {
+ renderer.setMode(_gl.LINES);
+ } else if (object.isLineLoop) {
+ renderer.setMode(_gl.LINE_LOOP);
+ } else {
+ renderer.setMode(_gl.LINE_STRIP);
+ }
+ } else if (object.isPoints) {
+ renderer.setMode(_gl.POINTS);
+ } else if (object.isSprite) {
+ renderer.setMode(_gl.TRIANGLES);
+ }
+
+ if (geometry && geometry.isInstancedBufferGeometry) {
+ if (geometry.maxInstancedCount > 0) {
+ renderer.renderInstances(geometry, drawStart, drawCount);
+ }
+ } else {
+ renderer.render(drawStart, drawCount);
+ }
+ };
+
+ function setupVertexAttributes(material, program, geometry) {
+ if (
+ geometry &&
+ geometry.isInstancedBufferGeometry &&
+ !capabilities.isWebGL2
+ ) {
+ if (extensions.get("ANGLE_instanced_arrays") === null) {
+ console.error(
+ "THREE.WebGLRenderer.setupVertexAttributes: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays."
+ );
+ return;
+ }
+ }
+
+ state.initAttributes();
+
+ var geometryAttributes = geometry.attributes;
+
+ var programAttributes = program.getAttributes();
+
+ var materialDefaultAttributeValues = material.defaultAttributeValues;
+
+ for (var name in programAttributes) {
+ var programAttribute = programAttributes[name];
+
+ if (programAttribute >= 0) {
+ var geometryAttribute = geometryAttributes[name];
+
+ if (geometryAttribute !== undefined) {
+ var normalized = geometryAttribute.normalized;
+ var size = geometryAttribute.itemSize;
+
+ var attribute = attributes.get(geometryAttribute);
+
+ // TODO Attribute may not be available on context restore
+
+ if (attribute === undefined) continue;
+
+ var buffer = attribute.buffer;
+ var type = attribute.type;
+ var bytesPerElement = attribute.bytesPerElement;
+
+ if (geometryAttribute.isInterleavedBufferAttribute) {
+ var data = geometryAttribute.data;
+ var stride = data.stride;
+ var offset = geometryAttribute.offset;
+
+ if (data && data.isInstancedInterleavedBuffer) {
+ state.enableAttributeAndDivisor(
+ programAttribute,
+ data.meshPerAttribute
+ );
+
+ if (geometry.maxInstancedCount === undefined) {
+ geometry.maxInstancedCount = data.meshPerAttribute * data.count;
+ }
+ } else {
+ state.enableAttribute(programAttribute);
+ }
+
+ _gl.bindBuffer(_gl.ARRAY_BUFFER, buffer);
+ _gl.vertexAttribPointer(
+ programAttribute,
+ size,
+ type,
+ normalized,
+ stride * bytesPerElement,
+ offset * bytesPerElement
+ );
+ } else {
+ if (geometryAttribute.isInstancedBufferAttribute) {
+ state.enableAttributeAndDivisor(
+ programAttribute,
+ geometryAttribute.meshPerAttribute
+ );
+
+ if (geometry.maxInstancedCount === undefined) {
+ geometry.maxInstancedCount =
+ geometryAttribute.meshPerAttribute * geometryAttribute.count;
+ }
+ } else {
+ state.enableAttribute(programAttribute);
+ }
+
+ _gl.bindBuffer(_gl.ARRAY_BUFFER, buffer);
+ _gl.vertexAttribPointer(
+ programAttribute,
+ size,
+ type,
+ normalized,
+ 0,
+ 0
+ );
+ }
+ } else if (materialDefaultAttributeValues !== undefined) {
+ var value = materialDefaultAttributeValues[name];
+
+ if (value !== undefined) {
+ switch (value.length) {
+ case 2:
+ _gl.vertexAttrib2fv(programAttribute, value);
+ break;
+
+ case 3:
+ _gl.vertexAttrib3fv(programAttribute, value);
+ break;
+
+ case 4:
+ _gl.vertexAttrib4fv(programAttribute, value);
+ break;
+
+ default:
+ _gl.vertexAttrib1fv(programAttribute, value);
+ }
+ }
+ }
+ }
+ }
+
+ state.disableUnusedAttributes();
+ }
+
+ // Compile
+
+ this.compile = function (scene, camera) {
+ currentRenderState = renderStates.get(scene, camera);
+ currentRenderState.init();
+
+ scene.traverse(function (object) {
+ if (object.isLight) {
+ currentRenderState.pushLight(object);
+
+ if (object.castShadow) {
+ currentRenderState.pushShadow(object);
+ }
+ }
+ });
+
+ currentRenderState.setupLights(camera);
+
+ scene.traverse(function (object) {
+ if (object.material) {
+ if (Array.isArray(object.material)) {
+ for (var i = 0; i < object.material.length; i++) {
+ initMaterial(object.material[i], scene.fog, object);
+ }
+ } else {
+ initMaterial(object.material, scene.fog, object);
+ }
+ }
+ });
+ };
+
+ // Animation Loop
+
+ var onAnimationFrameCallback = null;
+
+ function onAnimationFrame(time) {
+ if (vr.isPresenting()) return;
+ if (onAnimationFrameCallback) onAnimationFrameCallback(time);
+ }
+
+ var animation = new WebGLAnimation();
+ animation.setAnimationLoop(onAnimationFrame);
+
+ if (typeof window !== "undefined") animation.setContext(window);
+
+ this.setAnimationLoop = function (callback) {
+ onAnimationFrameCallback = callback;
+ vr.setAnimationLoop(callback);
+
+ animation.start();
+ };
+
+ // Rendering
+
+ this.render = function (scene, camera) {
+ var renderTarget, forceClear;
+
+ if (arguments[2] !== undefined) {
+ console.warn(
+ "THREE.WebGLRenderer.render(): the renderTarget argument has been removed. Use .setRenderTarget() instead."
+ );
+ renderTarget = arguments[2];
+ }
+
+ if (arguments[3] !== undefined) {
+ console.warn(
+ "THREE.WebGLRenderer.render(): the forceClear argument has been removed. Use .clear() instead."
+ );
+ forceClear = arguments[3];
+ }
+
+ if (!(camera && camera.isCamera)) {
+ console.error(
+ "THREE.WebGLRenderer.render: camera is not an instance of THREE.Camera."
+ );
+ return;
+ }
+
+ if (_isContextLost) return;
+
+ // reset caching for this frame
+
+ _currentGeometryProgram.geometry = null;
+ _currentGeometryProgram.program = null;
+ _currentGeometryProgram.wireframe = false;
+ _currentMaterialId = -1;
+ _currentCamera = null;
+
+ // update scene graph
+
+ if (scene.autoUpdate === true) scene.updateMatrixWorld();
+
+ // update camera matrices and frustum
+
+ if (camera.parent === null) camera.updateMatrixWorld();
+
+ if (vr.enabled) {
+ camera = vr.getCamera(camera);
+ }
+
+ //
+
+ currentRenderState = renderStates.get(scene, camera);
+ currentRenderState.init();
+
+ scene.onBeforeRender(
+ _this,
+ scene,
+ camera,
+ renderTarget || _currentRenderTarget
+ );
+
+ _projScreenMatrix.multiplyMatrices(
+ camera.projectionMatrix,
+ camera.matrixWorldInverse
+ );
+ _frustum.setFromMatrix(_projScreenMatrix);
+
+ _localClippingEnabled = this.localClippingEnabled;
+ _clippingEnabled = _clipping.init(
+ this.clippingPlanes,
+ _localClippingEnabled,
+ camera
+ );
+
+ currentRenderList = renderLists.get(scene, camera);
+ currentRenderList.init();
+
+ projectObject(scene, camera, 0, _this.sortObjects);
+
+ if (_this.sortObjects === true) {
+ currentRenderList.sort();
+ }
+
+ //
+
+ if (_clippingEnabled) _clipping.beginShadows();
+
+ var shadowsArray = currentRenderState.state.shadowsArray;
+
+ shadowMap.render(shadowsArray, scene, camera);
+
+ currentRenderState.setupLights(camera);
+
+ if (_clippingEnabled) _clipping.endShadows();
+
+ //
+
+ if (this.info.autoReset) this.info.reset();
+
+ if (renderTarget !== undefined) {
+ this.setRenderTarget(renderTarget);
+ }
+
+ //
+
+ background.render(currentRenderList, scene, camera, forceClear);
+
+ // render scene
+
+ var opaqueObjects = currentRenderList.opaque;
+ var transparentObjects = currentRenderList.transparent;
+
+ if (scene.overrideMaterial) {
+ var overrideMaterial = scene.overrideMaterial;
+
+ if (opaqueObjects.length)
+ renderObjects(opaqueObjects, scene, camera, overrideMaterial);
+ if (transparentObjects.length)
+ renderObjects(transparentObjects, scene, camera, overrideMaterial);
+ } else {
+ // opaque pass (front-to-back order)
+
+ if (opaqueObjects.length) renderObjects(opaqueObjects, scene, camera);
+
+ // transparent pass (back-to-front order)
+
+ if (transparentObjects.length)
+ renderObjects(transparentObjects, scene, camera);
+ }
+
+ //
+
+ scene.onAfterRender(_this, scene, camera);
+
+ //
+
+ if (_currentRenderTarget !== null) {
+ // Generate mipmap if we're using any kind of mipmap filtering
+
+ textures.updateRenderTargetMipmap(_currentRenderTarget);
+
+ // resolve multisample renderbuffers to a single-sample texture if necessary
+
+ textures.updateMultisampleRenderTarget(_currentRenderTarget);
+ }
+
+ // Ensure depth buffer writing is enabled so it can be cleared on next render
+
+ state.buffers.depth.setTest(true);
+ state.buffers.depth.setMask(true);
+ state.buffers.color.setMask(true);
+
+ state.setPolygonOffset(false);
+
+ if (vr.enabled) {
+ vr.submitFrame();
+ }
+
+ // _gl.finish();
+
+ currentRenderList = null;
+ currentRenderState = null;
+ };
+
+ function projectObject(object, camera, groupOrder, sortObjects) {
+ if (object.visible === false) return;
+
+ var visible = object.layers.test(camera.layers);
+
+ if (visible) {
+ if (object.isGroup) {
+ groupOrder = object.renderOrder;
+ } else if (object.isLOD) {
+ if (object.autoUpdate === true) object.update(camera);
+ } else if (object.isLight) {
+ currentRenderState.pushLight(object);
+
+ if (object.castShadow) {
+ currentRenderState.pushShadow(object);
+ }
+ } else if (object.isSprite) {
+ if (!object.frustumCulled || _frustum.intersectsSprite(object)) {
+ if (sortObjects) {
+ _vector3
+ .setFromMatrixPosition(object.matrixWorld)
+ .applyMatrix4(_projScreenMatrix);
+ }
+
+ var geometry = objects.update(object);
+ var material = object.material;
+
+ if (material.visible) {
+ currentRenderList.push(
+ object,
+ geometry,
+ material,
+ groupOrder,
+ _vector3.z,
+ null
+ );
+ }
+ }
+ } else if (object.isImmediateRenderObject) {
+ if (sortObjects) {
+ _vector3
+ .setFromMatrixPosition(object.matrixWorld)
+ .applyMatrix4(_projScreenMatrix);
+ }
+
+ currentRenderList.push(
+ object,
+ null,
+ object.material,
+ groupOrder,
+ _vector3.z,
+ null
+ );
+ } else if (object.isMesh || object.isLine || object.isPoints) {
+ if (object.isSkinnedMesh) {
+ object.skeleton.update();
+ }
+
+ if (!object.frustumCulled || _frustum.intersectsObject(object)) {
+ if (sortObjects) {
+ _vector3
+ .setFromMatrixPosition(object.matrixWorld)
+ .applyMatrix4(_projScreenMatrix);
+ }
+
+ var geometry = objects.update(object);
+ var material = object.material;
+
+ if (Array.isArray(material)) {
+ var groups = geometry.groups;
+
+ for (var i = 0, l = groups.length; i < l; i++) {
+ var group = groups[i];
+ var groupMaterial = material[group.materialIndex];
+
+ if (groupMaterial && groupMaterial.visible) {
+ currentRenderList.push(
+ object,
+ geometry,
+ groupMaterial,
+ groupOrder,
+ _vector3.z,
+ group
+ );
+ }
+ }
+ } else if (material.visible) {
+ currentRenderList.push(
+ object,
+ geometry,
+ material,
+ groupOrder,
+ _vector3.z,
+ null
+ );
+ }
+ }
+ }
+ }
+
+ var children = object.children;
+
+ for (var i = 0, l = children.length; i < l; i++) {
+ projectObject(children[i], camera, groupOrder, sortObjects);
+ }
+ }
+
+ function renderObjects(renderList, scene, camera, overrideMaterial) {
+ for (var i = 0, l = renderList.length; i < l; i++) {
+ var renderItem = renderList[i];
+
+ var object = renderItem.object;
+ var geometry = renderItem.geometry;
+ var material =
+ overrideMaterial === undefined ? renderItem.material : overrideMaterial;
+ var group = renderItem.group;
+
+ if (camera.isArrayCamera) {
+ _currentArrayCamera = camera;
+
+ var cameras = camera.cameras;
+
+ for (var j = 0, jl = cameras.length; j < jl; j++) {
+ var camera2 = cameras[j];
+
+ if (object.layers.test(camera2.layers)) {
+ state.viewport(_currentViewport.copy(camera2.viewport));
+
+ currentRenderState.setupLights(camera2);
+
+ renderObject(object, scene, camera2, geometry, material, group);
+ }
+ }
+ } else {
+ _currentArrayCamera = null;
+
+ renderObject(object, scene, camera, geometry, material, group);
+ }
+ }
+ }
+
+ function renderObject(object, scene, camera, geometry, material, group) {
+ object.onBeforeRender(_this, scene, camera, geometry, material, group);
+ currentRenderState = renderStates.get(scene, _currentArrayCamera || camera);
+
+ object.modelViewMatrix.multiplyMatrices(
+ camera.matrixWorldInverse,
+ object.matrixWorld
+ );
+ object.normalMatrix.getNormalMatrix(object.modelViewMatrix);
+
+ if (object.isImmediateRenderObject) {
+ state.setMaterial(material);
+
+ var program = setProgram(camera, scene.fog, material, object);
+
+ _currentGeometryProgram.geometry = null;
+ _currentGeometryProgram.program = null;
+ _currentGeometryProgram.wireframe = false;
+
+ renderObjectImmediate(object, program);
+ } else {
+ _this.renderBufferDirect(
+ camera,
+ scene.fog,
+ geometry,
+ material,
+ object,
+ group
+ );
+ }
+
+ object.onAfterRender(_this, scene, camera, geometry, material, group);
+ currentRenderState = renderStates.get(scene, _currentArrayCamera || camera);
+ }
+
+ function initMaterial(material, fog, object) {
+ var materialProperties = properties.get(material);
+
+ var lights = currentRenderState.state.lights;
+ var shadowsArray = currentRenderState.state.shadowsArray;
+
+ var lightsStateVersion = lights.state.version;
+
+ var parameters = programCache.getParameters(
+ material,
+ lights.state,
+ shadowsArray,
+ fog,
+ _clipping.numPlanes,
+ _clipping.numIntersection,
+ object
+ );
+
+ var code = programCache.getProgramCode(material, parameters);
+
+ var program = materialProperties.program;
+ var programChange = true;
+
+ if (program === undefined) {
+ // new material
+ material.addEventListener("dispose", onMaterialDispose);
+ } else if (program.code !== code) {
+ // changed glsl or parameters
+ releaseMaterialProgramReference(material);
+ } else if (materialProperties.lightsStateVersion !== lightsStateVersion) {
+ materialProperties.lightsStateVersion = lightsStateVersion;
+
+ programChange = false;
+ } else if (parameters.shaderID !== undefined) {
+ // same glsl and uniform list
+ return;
+ } else {
+ // only rebuild uniform list
+ programChange = false;
+ }
+
+ if (programChange) {
+ if (parameters.shaderID) {
+ var shader = ShaderLib[parameters.shaderID];
+
+ materialProperties.shader = {
+ name: material.type,
+ uniforms: cloneUniforms(shader.uniforms),
+ vertexShader: shader.vertexShader,
+ fragmentShader: shader.fragmentShader,
+ };
+ } else {
+ materialProperties.shader = {
+ name: material.type,
+ uniforms: material.uniforms,
+ vertexShader: material.vertexShader,
+ fragmentShader: material.fragmentShader,
+ };
+ }
+
+ material.onBeforeCompile(materialProperties.shader, _this);
+
+ // Computing code again as onBeforeCompile may have changed the shaders
+ code = programCache.getProgramCode(material, parameters);
+
+ program = programCache.acquireProgram(
+ material,
+ materialProperties.shader,
+ parameters,
+ code
+ );
+
+ materialProperties.program = program;
+ material.program = program;
+ }
+
+ var programAttributes = program.getAttributes();
+
+ if (material.morphTargets) {
+ material.numSupportedMorphTargets = 0;
+
+ for (var i = 0; i < _this.maxMorphTargets; i++) {
+ if (programAttributes["morphTarget" + i] >= 0) {
+ material.numSupportedMorphTargets++;
+ }
+ }
+ }
+
+ if (material.morphNormals) {
+ material.numSupportedMorphNormals = 0;
+
+ for (var i = 0; i < _this.maxMorphNormals; i++) {
+ if (programAttributes["morphNormal" + i] >= 0) {
+ material.numSupportedMorphNormals++;
+ }
+ }
+ }
+
+ var uniforms = materialProperties.shader.uniforms;
+
+ if (
+ (!material.isShaderMaterial && !material.isRawShaderMaterial) ||
+ material.clipping === true
+ ) {
+ materialProperties.numClippingPlanes = _clipping.numPlanes;
+ materialProperties.numIntersection = _clipping.numIntersection;
+ uniforms.clippingPlanes = _clipping.uniform;
+ }
+
+ materialProperties.fog = fog;
+
+ // store the light setup it was created for
+
+ materialProperties.lightsStateVersion = lightsStateVersion;
+
+ if (material.lights) {
+ // wire up the material to this renderer's lighting state
+
+ uniforms.ambientLightColor.value = lights.state.ambient;
+ uniforms.lightProbe.value = lights.state.probe;
+ uniforms.directionalLights.value = lights.state.directional;
+ uniforms.spotLights.value = lights.state.spot;
+ uniforms.rectAreaLights.value = lights.state.rectArea;
+ uniforms.pointLights.value = lights.state.point;
+ uniforms.hemisphereLights.value = lights.state.hemi;
+
+ uniforms.directionalShadowMap.value = lights.state.directionalShadowMap;
+ uniforms.directionalShadowMatrix.value =
+ lights.state.directionalShadowMatrix;
+ uniforms.spotShadowMap.value = lights.state.spotShadowMap;
+ uniforms.spotShadowMatrix.value = lights.state.spotShadowMatrix;
+ uniforms.pointShadowMap.value = lights.state.pointShadowMap;
+ uniforms.pointShadowMatrix.value = lights.state.pointShadowMatrix;
+ // TODO (abelnation): add area lights shadow info to uniforms
+ }
+
+ var progUniforms = materialProperties.program.getUniforms(),
+ uniformsList = WebGLUniforms.seqWithValue(progUniforms.seq, uniforms);
+
+ materialProperties.uniformsList = uniformsList;
+ }
+
+ function setProgram(camera, fog, material, object) {
+ textures.resetTextureUnits();
+
+ var materialProperties = properties.get(material);
+ var lights = currentRenderState.state.lights;
+
+ if (_clippingEnabled) {
+ if (_localClippingEnabled || camera !== _currentCamera) {
+ var useCache =
+ camera === _currentCamera && material.id === _currentMaterialId;
+
+ // we might want to call this function with some ClippingGroup
+ // object instead of the material, once it becomes feasible
+ // (#8465, #8379)
+ _clipping.setState(
+ material.clippingPlanes,
+ material.clipIntersection,
+ material.clipShadows,
+ camera,
+ materialProperties,
+ useCache
+ );
+ }
+ }
+
+ if (material.needsUpdate === false) {
+ if (materialProperties.program === undefined) {
+ material.needsUpdate = true;
+ } else if (material.fog && materialProperties.fog !== fog) {
+ material.needsUpdate = true;
+ } else if (
+ material.lights &&
+ materialProperties.lightsStateVersion !== lights.state.version
+ ) {
+ material.needsUpdate = true;
+ } else if (
+ materialProperties.numClippingPlanes !== undefined &&
+ (materialProperties.numClippingPlanes !== _clipping.numPlanes ||
+ materialProperties.numIntersection !== _clipping.numIntersection)
+ ) {
+ material.needsUpdate = true;
+ }
+ }
+
+ if (material.needsUpdate) {
+ initMaterial(material, fog, object);
+ material.needsUpdate = false;
+ }
+
+ var refreshProgram = false;
+ var refreshMaterial = false;
+ var refreshLights = false;
+
+ var program = materialProperties.program,
+ p_uniforms = program.getUniforms(),
+ m_uniforms = materialProperties.shader.uniforms;
+
+ if (state.useProgram(program.program)) {
+ refreshProgram = true;
+ refreshMaterial = true;
+ refreshLights = true;
+ }
+
+ if (material.id !== _currentMaterialId) {
+ _currentMaterialId = material.id;
+
+ refreshMaterial = true;
+ }
+
+ if (refreshProgram || _currentCamera !== camera) {
+ p_uniforms.setValue(_gl, "projectionMatrix", camera.projectionMatrix);
+
+ if (capabilities.logarithmicDepthBuffer) {
+ p_uniforms.setValue(
+ _gl,
+ "logDepthBufFC",
+ 2.0 / (Math.log(camera.far + 1.0) / Math.LN2)
+ );
+ }
+
+ if (_currentCamera !== camera) {
+ _currentCamera = camera;
+
+ // lighting uniforms depend on the camera so enforce an update
+ // now, in case this material supports lights - or later, when
+ // the next material that does gets activated:
+
+ refreshMaterial = true; // set to true on material change
+ refreshLights = true; // remains set until update done
+ }
+
+ // load material specific uniforms
+ // (shader material also gets them for the sake of genericity)
+
+ if (
+ material.isShaderMaterial ||
+ material.isMeshPhongMaterial ||
+ material.isMeshStandardMaterial ||
+ material.envMap
+ ) {
+ var uCamPos = p_uniforms.map.cameraPosition;
+
+ if (uCamPos !== undefined) {
+ uCamPos.setValue(
+ _gl,
+ _vector3.setFromMatrixPosition(camera.matrixWorld)
+ );
+ }
+ }
+
+ if (
+ material.isMeshPhongMaterial ||
+ material.isMeshLambertMaterial ||
+ material.isMeshBasicMaterial ||
+ material.isMeshStandardMaterial ||
+ material.isShaderMaterial ||
+ material.skinning
+ ) {
+ p_uniforms.setValue(_gl, "viewMatrix", camera.matrixWorldInverse);
+ }
+ }
+
+ // skinning uniforms must be set even if material didn't change
+ // auto-setting of texture unit for bone texture must go before other textures
+ // not sure why, but otherwise weird things happen
+
+ if (material.skinning) {
+ p_uniforms.setOptional(_gl, object, "bindMatrix");
+ p_uniforms.setOptional(_gl, object, "bindMatrixInverse");
+
+ var skeleton = object.skeleton;
+
+ if (skeleton) {
+ var bones = skeleton.bones;
+
+ if (capabilities.floatVertexTextures) {
+ if (skeleton.boneTexture === undefined) {
+ // layout (1 matrix = 4 pixels)
+ // RGBA RGBA RGBA RGBA (=> column1, column2, column3, column4)
+ // with 8x8 pixel texture max 16 bones * 4 pixels = (8 * 8)
+ // 16x16 pixel texture max 64 bones * 4 pixels = (16 * 16)
+ // 32x32 pixel texture max 256 bones * 4 pixels = (32 * 32)
+ // 64x64 pixel texture max 1024 bones * 4 pixels = (64 * 64)
+
+ var size = Math.sqrt(bones.length * 4); // 4 pixels needed for 1 matrix
+ size = _Math.ceilPowerOfTwo(size);
+ size = Math.max(size, 4);
+
+ var boneMatrices = new Float32Array(size * size * 4); // 4 floats per RGBA pixel
+ boneMatrices.set(skeleton.boneMatrices); // copy current values
+
+ var boneTexture = new DataTexture(
+ boneMatrices,
+ size,
+ size,
+ RGBAFormat,
+ FloatType
+ );
+ boneTexture.needsUpdate = true;
+
+ skeleton.boneMatrices = boneMatrices;
+ skeleton.boneTexture = boneTexture;
+ skeleton.boneTextureSize = size;
+ }
+
+ p_uniforms.setValue(
+ _gl,
+ "boneTexture",
+ skeleton.boneTexture,
+ textures
+ );
+ p_uniforms.setValue(_gl, "boneTextureSize", skeleton.boneTextureSize);
+ } else {
+ p_uniforms.setOptional(_gl, skeleton, "boneMatrices");
+ }
+ }
+ }
+
+ if (refreshMaterial) {
+ p_uniforms.setValue(
+ _gl,
+ "toneMappingExposure",
+ _this.toneMappingExposure
+ );
+ p_uniforms.setValue(
+ _gl,
+ "toneMappingWhitePoint",
+ _this.toneMappingWhitePoint
+ );
+
+ if (material.lights) {
+ // the current material requires lighting info
+
+ // note: all lighting uniforms are always set correctly
+ // they simply reference the renderer's state for their
+ // values
+ //
+ // use the current material's .needsUpdate flags to set
+ // the GL state when required
+
+ markUniformsLightsNeedsUpdate(m_uniforms, refreshLights);
+ }
+
+ // refresh uniforms common to several materials
+
+ if (fog && material.fog) {
+ refreshUniformsFog(m_uniforms, fog);
+ }
+
+ if (material.isMeshBasicMaterial) {
+ refreshUniformsCommon(m_uniforms, material);
+ } else if (material.isMeshLambertMaterial) {
+ refreshUniformsCommon(m_uniforms, material);
+ refreshUniformsLambert(m_uniforms, material);
+ } else if (material.isMeshPhongMaterial) {
+ refreshUniformsCommon(m_uniforms, material);
+
+ if (material.isMeshToonMaterial) {
+ refreshUniformsToon(m_uniforms, material);
+ } else {
+ refreshUniformsPhong(m_uniforms, material);
+ }
+ } else if (material.isMeshStandardMaterial) {
+ refreshUniformsCommon(m_uniforms, material);
+
+ if (material.isMeshPhysicalMaterial) {
+ refreshUniformsPhysical(m_uniforms, material);
+ } else {
+ refreshUniformsStandard(m_uniforms, material);
+ }
+ } else if (material.isMeshMatcapMaterial) {
+ refreshUniformsCommon(m_uniforms, material);
+
+ refreshUniformsMatcap(m_uniforms, material);
+ } else if (material.isMeshDepthMaterial) {
+ refreshUniformsCommon(m_uniforms, material);
+ refreshUniformsDepth(m_uniforms, material);
+ } else if (material.isMeshDistanceMaterial) {
+ refreshUniformsCommon(m_uniforms, material);
+ refreshUniformsDistance(m_uniforms, material);
+ } else if (material.isMeshNormalMaterial) {
+ refreshUniformsCommon(m_uniforms, material);
+ refreshUniformsNormal(m_uniforms, material);
+ } else if (material.isLineBasicMaterial) {
+ refreshUniformsLine(m_uniforms, material);
+
+ if (material.isLineDashedMaterial) {
+ refreshUniformsDash(m_uniforms, material);
+ }
+ } else if (material.isPointsMaterial) {
+ refreshUniformsPoints(m_uniforms, material);
+ } else if (material.isSpriteMaterial) {
+ refreshUniformsSprites(m_uniforms, material);
+ } else if (material.isShadowMaterial) {
+ m_uniforms.color.value.copy(material.color);
+ m_uniforms.opacity.value = material.opacity;
+ }
+
+ // RectAreaLight Texture
+ // TODO (mrdoob): Find a nicer implementation
+
+ if (m_uniforms.ltc_1 !== undefined)
+ m_uniforms.ltc_1.value = UniformsLib.LTC_1;
+ if (m_uniforms.ltc_2 !== undefined)
+ m_uniforms.ltc_2.value = UniformsLib.LTC_2;
+
+ WebGLUniforms.upload(
+ _gl,
+ materialProperties.uniformsList,
+ m_uniforms,
+ textures
+ );
+ }
+
+ if (material.isShaderMaterial && material.uniformsNeedUpdate === true) {
+ WebGLUniforms.upload(
+ _gl,
+ materialProperties.uniformsList,
+ m_uniforms,
+ textures
+ );
+ material.uniformsNeedUpdate = false;
+ }
+
+ if (material.isSpriteMaterial) {
+ p_uniforms.setValue(_gl, "center", object.center);
+ }
+
+ // common matrices
+
+ p_uniforms.setValue(_gl, "modelViewMatrix", object.modelViewMatrix);
+ p_uniforms.setValue(_gl, "normalMatrix", object.normalMatrix);
+ p_uniforms.setValue(_gl, "modelMatrix", object.matrixWorld);
+
+ return program;
+ }
+
+ // Uniforms (refresh uniforms objects)
+
+ function refreshUniformsCommon(uniforms, material) {
+ uniforms.opacity.value = material.opacity;
+
+ if (material.color) {
+ uniforms.diffuse.value.copy(material.color);
+ }
+
+ if (material.emissive) {
+ uniforms.emissive.value
+ .copy(material.emissive)
+ .multiplyScalar(material.emissiveIntensity);
+ }
+
+ if (material.map) {
+ uniforms.map.value = material.map;
+ }
+
+ if (material.alphaMap) {
+ uniforms.alphaMap.value = material.alphaMap;
+ }
+
+ if (material.specularMap) {
+ uniforms.specularMap.value = material.specularMap;
+ }
+
+ if (material.envMap) {
+ uniforms.envMap.value = material.envMap;
+
+ // don't flip CubeTexture envMaps, flip everything else:
+ // WebGLRenderTargetCube will be flipped for backwards compatibility
+ // WebGLRenderTargetCube.texture will be flipped because it's a Texture and NOT a CubeTexture
+ // this check must be handled differently, or removed entirely, if WebGLRenderTargetCube uses a CubeTexture in the future
+ uniforms.flipEnvMap.value = material.envMap.isCubeTexture ? -1 : 1;
+
+ uniforms.reflectivity.value = material.reflectivity;
+ uniforms.refractionRatio.value = material.refractionRatio;
+
+ uniforms.maxMipLevel.value = properties.get(
+ material.envMap
+ ).__maxMipLevel;
+ }
+
+ if (material.lightMap) {
+ uniforms.lightMap.value = material.lightMap;
+ uniforms.lightMapIntensity.value = material.lightMapIntensity;
+ }
+
+ if (material.aoMap) {
+ uniforms.aoMap.value = material.aoMap;
+ uniforms.aoMapIntensity.value = material.aoMapIntensity;
+ }
+
+ // uv repeat and offset setting priorities
+ // 1. color map
+ // 2. specular map
+ // 3. normal map
+ // 4. bump map
+ // 5. alpha map
+ // 6. emissive map
+
+ var uvScaleMap;
+
+ if (material.map) {
+ uvScaleMap = material.map;
+ } else if (material.specularMap) {
+ uvScaleMap = material.specularMap;
+ } else if (material.displacementMap) {
+ uvScaleMap = material.displacementMap;
+ } else if (material.normalMap) {
+ uvScaleMap = material.normalMap;
+ } else if (material.bumpMap) {
+ uvScaleMap = material.bumpMap;
+ } else if (material.roughnessMap) {
+ uvScaleMap = material.roughnessMap;
+ } else if (material.metalnessMap) {
+ uvScaleMap = material.metalnessMap;
+ } else if (material.alphaMap) {
+ uvScaleMap = material.alphaMap;
+ } else if (material.emissiveMap) {
+ uvScaleMap = material.emissiveMap;
+ }
+
+ if (uvScaleMap !== undefined) {
+ // backwards compatibility
+ if (uvScaleMap.isWebGLRenderTarget) {
+ uvScaleMap = uvScaleMap.texture;
+ }
+
+ if (uvScaleMap.matrixAutoUpdate === true) {
+ uvScaleMap.updateMatrix();
+ }
+
+ uniforms.uvTransform.value.copy(uvScaleMap.matrix);
+ }
+ }
+
+ function refreshUniformsLine(uniforms, material) {
+ uniforms.diffuse.value.copy(material.color);
+ uniforms.opacity.value = material.opacity;
+ }
+
+ function refreshUniformsDash(uniforms, material) {
+ uniforms.dashSize.value = material.dashSize;
+ uniforms.totalSize.value = material.dashSize + material.gapSize;
+ uniforms.scale.value = material.scale;
+ }
+
+ function refreshUniformsPoints(uniforms, material) {
+ uniforms.diffuse.value.copy(material.color);
+ uniforms.opacity.value = material.opacity;
+ uniforms.size.value = material.size * _pixelRatio;
+ uniforms.scale.value = _height * 0.5;
+
+ uniforms.map.value = material.map;
+
+ if (material.map !== null) {
+ if (material.map.matrixAutoUpdate === true) {
+ material.map.updateMatrix();
+ }
+
+ uniforms.uvTransform.value.copy(material.map.matrix);
+ }
+ }
+
+ function refreshUniformsSprites(uniforms, material) {
+ uniforms.diffuse.value.copy(material.color);
+ uniforms.opacity.value = material.opacity;
+ uniforms.rotation.value = material.rotation;
+ uniforms.map.value = material.map;
+
+ if (material.map !== null) {
+ if (material.map.matrixAutoUpdate === true) {
+ material.map.updateMatrix();
+ }
+
+ uniforms.uvTransform.value.copy(material.map.matrix);
+ }
+ }
+
+ function refreshUniformsFog(uniforms, fog) {
+ uniforms.fogColor.value.copy(fog.color);
+
+ if (fog.isFog) {
+ uniforms.fogNear.value = fog.near;
+ uniforms.fogFar.value = fog.far;
+ } else if (fog.isFogExp2) {
+ uniforms.fogDensity.value = fog.density;
+ }
+ }
+
+ function refreshUniformsLambert(uniforms, material) {
+ if (material.emissiveMap) {
+ uniforms.emissiveMap.value = material.emissiveMap;
+ }
+ }
+
+ function refreshUniformsPhong(uniforms, material) {
+ uniforms.specular.value.copy(material.specular);
+ uniforms.shininess.value = Math.max(material.shininess, 1e-4); // to prevent pow( 0.0, 0.0 )
+
+ if (material.emissiveMap) {
+ uniforms.emissiveMap.value = material.emissiveMap;
+ }
+
+ if (material.bumpMap) {
+ uniforms.bumpMap.value = material.bumpMap;
+ uniforms.bumpScale.value = material.bumpScale;
+ if (material.side === BackSide) uniforms.bumpScale.value *= -1;
+ }
+
+ if (material.normalMap) {
+ uniforms.normalMap.value = material.normalMap;
+ uniforms.normalScale.value.copy(material.normalScale);
+ if (material.side === BackSide) uniforms.normalScale.value.negate();
+ }
+
+ if (material.displacementMap) {
+ uniforms.displacementMap.value = material.displacementMap;
+ uniforms.displacementScale.value = material.displacementScale;
+ uniforms.displacementBias.value = material.displacementBias;
+ }
+ }
+
+ function refreshUniformsToon(uniforms, material) {
+ refreshUniformsPhong(uniforms, material);
+
+ if (material.gradientMap) {
+ uniforms.gradientMap.value = material.gradientMap;
+ }
+ }
+
+ function refreshUniformsStandard(uniforms, material) {
+ uniforms.roughness.value = material.roughness;
+ uniforms.metalness.value = material.metalness;
+
+ if (material.roughnessMap) {
+ uniforms.roughnessMap.value = material.roughnessMap;
+ }
+
+ if (material.metalnessMap) {
+ uniforms.metalnessMap.value = material.metalnessMap;
+ }
+
+ if (material.emissiveMap) {
+ uniforms.emissiveMap.value = material.emissiveMap;
+ }
+
+ if (material.bumpMap) {
+ uniforms.bumpMap.value = material.bumpMap;
+ uniforms.bumpScale.value = material.bumpScale;
+ if (material.side === BackSide) uniforms.bumpScale.value *= -1;
+ }
+
+ if (material.normalMap) {
+ uniforms.normalMap.value = material.normalMap;
+ uniforms.normalScale.value.copy(material.normalScale);
+ if (material.side === BackSide) uniforms.normalScale.value.negate();
+ }
+
+ if (material.displacementMap) {
+ uniforms.displacementMap.value = material.displacementMap;
+ uniforms.displacementScale.value = material.displacementScale;
+ uniforms.displacementBias.value = material.displacementBias;
+ }
+
+ if (material.envMap) {
+ //uniforms.envMap.value = material.envMap; // part of uniforms common
+ uniforms.envMapIntensity.value = material.envMapIntensity;
+ }
+ }
+
+ function refreshUniformsPhysical(uniforms, material) {
+ refreshUniformsStandard(uniforms, material);
+
+ uniforms.reflectivity.value = material.reflectivity; // also part of uniforms common
+
+ uniforms.clearcoat.value = material.clearcoat;
+ uniforms.clearcoatRoughness.value = material.clearcoatRoughness;
+ if (material.sheen) uniforms.sheen.value.copy(material.sheen);
+
+ if (material.clearcoatNormalMap) {
+ uniforms.clearcoatNormalScale.value.copy(material.clearcoatNormalScale);
+ uniforms.clearcoatNormalMap.value = material.clearcoatNormalMap;
+
+ if (material.side === BackSide) {
+ uniforms.clearcoatNormalScale.value.negate();
+ }
+ }
+
+ uniforms.transparency.value = material.transparency;
+ }
+
+ function refreshUniformsMatcap(uniforms, material) {
+ if (material.matcap) {
+ uniforms.matcap.value = material.matcap;
+ }
+
+ if (material.bumpMap) {
+ uniforms.bumpMap.value = material.bumpMap;
+ uniforms.bumpScale.value = material.bumpScale;
+ if (material.side === BackSide) uniforms.bumpScale.value *= -1;
+ }
+
+ if (material.normalMap) {
+ uniforms.normalMap.value = material.normalMap;
+ uniforms.normalScale.value.copy(material.normalScale);
+ if (material.side === BackSide) uniforms.normalScale.value.negate();
+ }
+
+ if (material.displacementMap) {
+ uniforms.displacementMap.value = material.displacementMap;
+ uniforms.displacementScale.value = material.displacementScale;
+ uniforms.displacementBias.value = material.displacementBias;
+ }
+ }
+
+ function refreshUniformsDepth(uniforms, material) {
+ if (material.displacementMap) {
+ uniforms.displacementMap.value = material.displacementMap;
+ uniforms.displacementScale.value = material.displacementScale;
+ uniforms.displacementBias.value = material.displacementBias;
+ }
+ }
+
+ function refreshUniformsDistance(uniforms, material) {
+ if (material.displacementMap) {
+ uniforms.displacementMap.value = material.displacementMap;
+ uniforms.displacementScale.value = material.displacementScale;
+ uniforms.displacementBias.value = material.displacementBias;
+ }
+
+ uniforms.referencePosition.value.copy(material.referencePosition);
+ uniforms.nearDistance.value = material.nearDistance;
+ uniforms.farDistance.value = material.farDistance;
+ }
+
+ function refreshUniformsNormal(uniforms, material) {
+ if (material.bumpMap) {
+ uniforms.bumpMap.value = material.bumpMap;
+ uniforms.bumpScale.value = material.bumpScale;
+ if (material.side === BackSide) uniforms.bumpScale.value *= -1;
+ }
+
+ if (material.normalMap) {
+ uniforms.normalMap.value = material.normalMap;
+ uniforms.normalScale.value.copy(material.normalScale);
+ if (material.side === BackSide) uniforms.normalScale.value.negate();
+ }
+
+ if (material.displacementMap) {
+ uniforms.displacementMap.value = material.displacementMap;
+ uniforms.displacementScale.value = material.displacementScale;
+ uniforms.displacementBias.value = material.displacementBias;
+ }
+ }
+
+ // If uniforms are marked as clean, they don't need to be loaded to the GPU.
+
+ function markUniformsLightsNeedsUpdate(uniforms, value) {
+ uniforms.ambientLightColor.needsUpdate = value;
+ uniforms.lightProbe.needsUpdate = value;
+
+ uniforms.directionalLights.needsUpdate = value;
+ uniforms.pointLights.needsUpdate = value;
+ uniforms.spotLights.needsUpdate = value;
+ uniforms.rectAreaLights.needsUpdate = value;
+ uniforms.hemisphereLights.needsUpdate = value;
+ }
+
+ //
+ this.setFramebuffer = function (value) {
+ if (_framebuffer !== value) _gl.bindFramebuffer(_gl.FRAMEBUFFER, value);
+
+ _framebuffer = value;
+ };
+
+ this.getActiveCubeFace = function () {
+ return _currentActiveCubeFace;
+ };
+
+ this.getActiveMipmapLevel = function () {
+ return _currentActiveMipmapLevel;
+ };
+
+ this.getRenderTarget = function () {
+ return _currentRenderTarget;
+ };
+
+ this.setRenderTarget = function (
+ renderTarget,
+ activeCubeFace,
+ activeMipmapLevel
+ ) {
+ _currentRenderTarget = renderTarget;
+ _currentActiveCubeFace = activeCubeFace;
+ _currentActiveMipmapLevel = activeMipmapLevel;
+
+ if (
+ renderTarget &&
+ properties.get(renderTarget).__webglFramebuffer === undefined
+ ) {
+ textures.setupRenderTarget(renderTarget);
+ }
+
+ var framebuffer = _framebuffer;
+ var isCube = false;
+
+ if (renderTarget) {
+ var __webglFramebuffer = properties.get(renderTarget).__webglFramebuffer;
+
+ if (renderTarget.isWebGLRenderTargetCube) {
+ framebuffer = __webglFramebuffer[activeCubeFace || 0];
+ isCube = true;
+ } else if (renderTarget.isWebGLMultisampleRenderTarget) {
+ framebuffer =
+ properties.get(renderTarget).__webglMultisampledFramebuffer;
+ } else {
+ framebuffer = __webglFramebuffer;
+ }
+
+ _currentViewport.copy(renderTarget.viewport);
+ _currentScissor.copy(renderTarget.scissor);
+ _currentScissorTest = renderTarget.scissorTest;
+ } else {
+ _currentViewport.copy(_viewport).multiplyScalar(_pixelRatio).floor();
+ _currentScissor.copy(_scissor).multiplyScalar(_pixelRatio).floor();
+ _currentScissorTest = _scissorTest;
+ }
+
+ if (_currentFramebuffer !== framebuffer) {
+ _gl.bindFramebuffer(_gl.FRAMEBUFFER, framebuffer);
+ _currentFramebuffer = framebuffer;
+ }
+
+ state.viewport(_currentViewport);
+ state.scissor(_currentScissor);
+ state.setScissorTest(_currentScissorTest);
+
+ if (isCube) {
+ var textureProperties = properties.get(renderTarget.texture);
+ _gl.framebufferTexture2D(
+ _gl.FRAMEBUFFER,
+ _gl.COLOR_ATTACHMENT0,
+ _gl.TEXTURE_CUBE_MAP_POSITIVE_X + (activeCubeFace || 0),
+ textureProperties.__webglTexture,
+ activeMipmapLevel || 0
+ );
+ }
+ };
+
+ this.readRenderTargetPixels = function (
+ renderTarget,
+ x,
+ y,
+ width,
+ height,
+ buffer,
+ activeCubeFaceIndex
+ ) {
+ if (!(renderTarget && renderTarget.isWebGLRenderTarget)) {
+ console.error(
+ "THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget."
+ );
+ return;
+ }
+
+ var framebuffer = properties.get(renderTarget).__webglFramebuffer;
+
+ if (
+ renderTarget.isWebGLRenderTargetCube &&
+ activeCubeFaceIndex !== undefined
+ ) {
+ framebuffer = framebuffer[activeCubeFaceIndex];
+ }
+
+ if (framebuffer) {
+ var restore = false;
+
+ if (framebuffer !== _currentFramebuffer) {
+ _gl.bindFramebuffer(_gl.FRAMEBUFFER, framebuffer);
+
+ restore = true;
+ }
+
+ try {
+ var texture = renderTarget.texture;
+ var textureFormat = texture.format;
+ var textureType = texture.type;
+
+ if (
+ textureFormat !== RGBAFormat &&
+ utils.convert(textureFormat) !==
+ _gl.getParameter(_gl.IMPLEMENTATION_COLOR_READ_FORMAT)
+ ) {
+ console.error(
+ "THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format."
+ );
+ return;
+ }
+
+ if (
+ textureType !== UnsignedByteType &&
+ utils.convert(textureType) !==
+ _gl.getParameter(_gl.IMPLEMENTATION_COLOR_READ_TYPE) && // IE11, Edge and Chrome Mac < 52 (#9513)
+ !(
+ textureType === FloatType &&
+ (capabilities.isWebGL2 ||
+ extensions.get("OES_texture_float") ||
+ extensions.get("WEBGL_color_buffer_float"))
+ ) && // Chrome Mac >= 52 and Firefox
+ !(
+ textureType === HalfFloatType &&
+ (capabilities.isWebGL2
+ ? extensions.get("EXT_color_buffer_float")
+ : extensions.get("EXT_color_buffer_half_float"))
+ )
+ ) {
+ console.error(
+ "THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type."
+ );
+ return;
+ }
+
+ if (
+ _gl.checkFramebufferStatus(_gl.FRAMEBUFFER) ===
+ _gl.FRAMEBUFFER_COMPLETE
+ ) {
+ // the following if statement ensures valid read requests (no out-of-bounds pixels, see #8604)
+
+ if (
+ x >= 0 &&
+ x <= renderTarget.width - width &&
+ y >= 0 &&
+ y <= renderTarget.height - height
+ ) {
+ _gl.readPixels(
+ x,
+ y,
+ width,
+ height,
+ utils.convert(textureFormat),
+ utils.convert(textureType),
+ buffer
+ );
+ }
+ } else {
+ console.error(
+ "THREE.WebGLRenderer.readRenderTargetPixels: readPixels from renderTarget failed. Framebuffer not complete."
+ );
+ }
+ } finally {
+ if (restore) {
+ _gl.bindFramebuffer(_gl.FRAMEBUFFER, _currentFramebuffer);
+ }
+ }
+ }
+ };
+
+ this.copyFramebufferToTexture = function (position, texture, level) {
+ var width = texture.image.width;
+ var height = texture.image.height;
+ var glFormat = utils.convert(texture.format);
+
+ textures.setTexture2D(texture, 0);
+
+ _gl.copyTexImage2D(
+ _gl.TEXTURE_2D,
+ level || 0,
+ glFormat,
+ position.x,
+ position.y,
+ width,
+ height,
+ 0
+ );
+ };
+
+ this.copyTextureToTexture = function (
+ position,
+ srcTexture,
+ dstTexture,
+ level
+ ) {
+ var width = srcTexture.image.width;
+ var height = srcTexture.image.height;
+ var glFormat = utils.convert(dstTexture.format);
+ var glType = utils.convert(dstTexture.type);
+
+ textures.setTexture2D(dstTexture, 0);
+
+ if (srcTexture.isDataTexture) {
+ _gl.texSubImage2D(
+ _gl.TEXTURE_2D,
+ level || 0,
+ position.x,
+ position.y,
+ width,
+ height,
+ glFormat,
+ glType,
+ srcTexture.image.data
+ );
+ } else {
+ _gl.texSubImage2D(
+ _gl.TEXTURE_2D,
+ level || 0,
+ position.x,
+ position.y,
+ glFormat,
+ glType,
+ srcTexture.image
+ );
+ }
+ };
+
+ if (typeof __THREE_DEVTOOLS__ !== "undefined") {
+ __THREE_DEVTOOLS__.dispatchEvent(
+ new CustomEvent("observe", { detail: this })
+ ); // eslint-disable-line no-undef
+ }
+}
+
+export { WebGLRenderer };
diff --git a/src/test/fixtures/label-continue-break-bug.js b/src/test/fixtures/label-continue-break-bug.js
new file mode 100644
index 000000000..12e46cca3
--- /dev/null
+++ b/src/test/fixtures/label-continue-break-bug.js
@@ -0,0 +1,125 @@
+Object.assign(Interpolant.prototype, {
+ evaluate: function (t) {
+ var pp = this.parameterPositions,
+ i1 = this._cachedIndex,
+ t1 = pp[i1],
+ t0 = pp[i1 - 1];
+
+ validate_interval: {
+ seek: {
+ var right;
+
+ linear_scan: {
+ //- See http://jsperf.com/comparison-to-undefined/3
+ //- slower code:
+ //-
+ //- if ( t >= t1 || t1 === undefined ) {
+ forward_scan: if (!(t < t1)) {
+ for (var giveUpAt = i1 + 2; ; ) {
+ if (t1 === undefined) {
+ if (t < t0) break forward_scan;
+
+ // after end
+
+ i1 = pp.length;
+ this._cachedIndex = i1;
+ return this.afterEnd_(i1 - 1, t, t0);
+ }
+
+ if (i1 === giveUpAt) break; // this loop
+
+ t0 = t1;
+ t1 = pp[++i1];
+
+ if (t < t1) {
+ // we have arrived at the sought interval
+ break seek;
+ }
+ }
+
+ // prepare binary search on the right side of the index
+ right = pp.length;
+ break linear_scan;
+ }
+
+ //- slower code:
+ //- if ( t < t0 || t0 === undefined ) {
+ if (!(t >= t0)) {
+ // looping?
+
+ var t1global = pp[1];
+
+ if (t < t1global) {
+ i1 = 2; // + 1, using the scan for the details
+ t0 = t1global;
+ }
+
+ // linear reverse scan
+
+ for (var giveUpAt = i1 - 2; ; ) {
+ if (t0 === undefined) {
+ // before start
+
+ this._cachedIndex = 0;
+ return this.beforeStart_(0, t, t1);
+ }
+
+ if (i1 === giveUpAt) break; // this loop
+
+ t1 = t0;
+ t0 = pp[--i1 - 1];
+
+ if (t >= t0) {
+ // we have arrived at the sought interval
+ break seek;
+ }
+ }
+
+ // prepare binary search on the left side of the index
+ right = i1;
+ i1 = 0;
+ break linear_scan;
+ }
+
+ // the interval is valid
+
+ break validate_interval;
+ } // linear scan
+
+ // binary search
+
+ while (i1 < right) {
+ var mid = (i1 + right) >>> 1;
+
+ if (t < pp[mid]) {
+ right = mid;
+ } else {
+ i1 = mid + 1;
+ }
+ }
+
+ t1 = pp[i1];
+ t0 = pp[i1 - 1];
+
+ // check boundary cases, again
+
+ if (t0 === undefined) {
+ this._cachedIndex = 0;
+ return this.beforeStart_(0, t, t1);
+ }
+
+ if (t1 === undefined) {
+ i1 = pp.length;
+ this._cachedIndex = i1;
+ return this.afterEnd_(i1 - 1, t0, t);
+ }
+ } // seek
+
+ this._cachedIndex = i1;
+
+ this.intervalChanged_(i1, t0, t1);
+ } // validate_interval
+
+ return this.interpolate_(i1, t0, t, t1);
+ },
+});
diff --git a/src/test/fixtures/symbols-bug.js b/src/test/fixtures/symbols-bug.js
new file mode 100644
index 000000000..f40ec8c50
--- /dev/null
+++ b/src/test/fixtures/symbols-bug.js
@@ -0,0 +1,16 @@
+var boom = {
+ a: 2,
+ b: "4",
+ c: "6",
+ d: "8",
+ e: "10",
+ f: 12,
+ g: 14,
+}["15"];
+
+const foo = "bacon";
+const james = "not-bacon";
+const lordy = "sammy";
+const boop = {
+ hey: { foo },
+};
diff --git a/src/test/tester.zig b/src/test/tester.zig
index 6b3174eac..81e1c8ea2 100644
--- a/src/test/tester.zig
+++ b/src/test/tester.zig
@@ -66,6 +66,10 @@ pub const Tester = struct {
}
pub fn evaluate_outcome(self: *const @This()) Outcome {
+ if (self.expected.len > self.result.len) {
+ return .fail;
+ }
+
for (self.expected) |char, i| {
if (char != self.result[i]) {
return Outcome.fail;