inching closure

This commit is contained in:
Jarred Sumner
2021-04-30 15:34:31 -07:00
parent fd56d41c8e
commit 107310d785
8 changed files with 605 additions and 102 deletions

34
.vscode/launch.json vendored
View File

@@ -1,38 +1,6 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "(lldb) Launch",
"type": "cppdbg",
"request": "launch",
"program": "/Users/jarredsumner/Code/esdev/src/zig-cache/o/b57013855157d9a38baa6327511eaf3e/test",
"cwd": "${workspaceFolder}",
"args": ["/Users/jarredsumner/Builds/zig/build/bin/zig"],
"stopAtEntry": false,
"environment": [],
"miDebuggerPath": "/usr/local/bin/lldb-mi",
"MIMode": "lldb",
"targetArchitecture": "x64",
"externalConsole": false
},
{
"name": "Launch",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/zig-cache/bin/esdev",
"args": ["/Users/jarredsumner/Code/devserverless/build.js"],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
// "preLaunchTask": "build",
"MIMode": "lldb",
"internalConsoleOptions": "openOnSessionStart",
"logging": {
"moduleLoad": false
}
}
]
}

View File

@@ -133,6 +133,8 @@ import { map } from "lodash-es";
const foo = map(["bar", "baz"], (item) => {});
```
If
##### HMR & Fast Refresh implementation
This section only applies when Hot Module Reloading is enabled. When it's off, none of this part runs. React Fast Refresh depends on Hot Module Reloading.

View File

@@ -2713,8 +2713,8 @@ pub const Ast = struct {
// is conveniently fully parallelized.
named_imports: std.AutoHashMap(Ref, NamedImport) = undefined,
named_exports: std.StringHashMap(NamedExport) = undefined,
top_level_symbol_to_parts: std.AutoHashMap(Ref, []u32) = undefined,
export_star_import_records: std.ArrayList(u32) = undefined,
top_level_symbol_to_parts: std.AutoHashMap(Ref, std.ArrayList(u32)) = undefined,
export_star_import_records: []u32 = &([_]u32{}),
pub fn initTest(parts: []Part) Ast {
return Ast{
@@ -2855,20 +2855,20 @@ pub const Part = struct {
scopes: []*Scope = &([_]*Scope{}),
// Each is an index into the file-level import record list
import_record_indices: std.ArrayList(u32) = undefined,
import_record_indices: []u32 = &([_]u32{}),
// All symbols that are declared in this part. Note that a given symbol may
// have multiple declarations, and so may end up being declared in multiple
// parts (e.g. multiple "var" declarations with the same name). Also note
// that this list isn't deduplicated and may contain duplicates.
declared_symbols: std.ArrayList(DeclaredSymbol) = undefined,
declared_symbols: []DeclaredSymbol = &([_]DeclaredSymbol{}),
// An estimate of the number of uses of all symbols used within this part.
symbol_uses: std.AutoHashMap(Ref, Symbol.Use) = undefined,
symbol_uses: SymbolUseMap = undefined,
// The indices of the other parts in this file that are needed if this part
// is needed.
dependencies: std.ArrayList(Dependency) = undefined,
dependencies: []Dependency = &([_]Dependency{}),
// If true, this part can be removed if none of the declared symbols are
// used. If the file containing this part is imported, then all parts that
@@ -2883,6 +2883,7 @@ pub const Part = struct {
// This is true if this file has been marked as live by the tree shaking
// algorithm.
is_live: bool = false,
pub const SymbolUseMap = std.AutoHashMap(Ref, Symbol.Use);
};
pub const Result = struct {
@@ -2897,7 +2898,7 @@ pub const StmtOrExpr = union(enum) {
pub const NamedImport = struct {
// Parts within this file that use this import
local_parts_with_uses: ?[]u32,
local_parts_with_uses: []u32 = &([_]u32{}),
alias: ?string,
alias_loc: ?logger.Loc,

View File

@@ -21,6 +21,8 @@ pub const PropertyModifierKeyword = tables.PropertyModifierKeyword;
pub const TypescriptStmtKeyword = tables.TypescriptStmtKeyword;
pub const TypeScriptAccessibilityModifier = tables.TypeScriptAccessibilityModifier;
pub var emptyJavaScriptString = ([_]u16{0});
pub const JSONOptions = struct {
allow_comments: bool = false,
allow_trailing_commas: bool = false,
@@ -906,12 +908,13 @@ pub const Lexer = struct {
}
pub fn unexpected(lexer: *@This()) void {
var found: string = undefined;
if (lexer.start == lexer.source.contents.len) {
found = "end of file";
} else {
found = lexer.raw();
}
const found = finder: {
if (lexer.start == lexer.source.contents.len) {
break :finder "end of file";
} else {
break :finder lexer.raw();
}
};
lexer.addRangeError(lexer.range(), "Unexpected {s}", .{found}, true);
}
@@ -925,10 +928,14 @@ pub const Lexer = struct {
}
pub fn expectedString(self: *@This(), text: string) void {
var found = self.raw();
if (self.source.contents.len == self.start) {
found = "end of file";
}
const found = finder: {
if (self.source.contents.len != self.start) {
break :finder self.raw();
} else {
break :finder "end of file";
}
};
self.addRangeError(self.range(), "Expected {s} but found {s}", .{ text, found }, true);
}
@@ -969,7 +976,7 @@ pub const Lexer = struct {
}
pub fn initGlobalName(log: *logger.Log, source: *logger.Source, allocator: *std.mem.Allocator) !@This() {
var empty_string_literal: JavascriptString = undefined;
var empty_string_literal: JavascriptString = emptyJavaScriptString;
var lex = @This(){
.log = log,
.source = source.*,
@@ -986,7 +993,7 @@ pub const Lexer = struct {
}
pub fn initTSConfig(log: *logger.Log, source: *logger.Source, allocator: *std.mem.Allocator) !@This() {
var empty_string_literal: JavascriptString = undefined;
var empty_string_literal: JavascriptString = emptyJavaScriptString;
var lex = @This(){
.log = log,
.source = source.*,
@@ -1006,7 +1013,7 @@ pub const Lexer = struct {
}
pub fn initJSON(log: *logger.Log, source: *logger.Source, allocator: *std.mem.Allocator) !@This() {
var empty_string_literal: JavascriptString = undefined;
var empty_string_literal: JavascriptString = &emptyJavaScriptString;
var lex = @This(){
.log = log,
.source = source.*,
@@ -1026,7 +1033,7 @@ pub const Lexer = struct {
}
pub fn init(log: *logger.Log, source: *logger.Source, allocator: *std.mem.Allocator) !@This() {
var empty_string_literal: JavascriptString = undefined;
var empty_string_literal: JavascriptString = &emptyJavaScriptString;
var lex = @This(){
.log = log,
.source = source.*,

View File

@@ -53,16 +53,16 @@ pub const ImportScanner = struct {
stmts: []Stmt = &([_]Stmt{}),
kept_import_equals: bool = false,
removed_import_equals: bool = false,
pub fn scan(p: *P, _stmts: []Stmt) ImportScanner {
pub fn scan(p: *P, stmts: []Stmt) !ImportScanner {
var scanner = ImportScanner{};
var stmts = StmtList.fromOwnedSlice(p.allocator, _stmts);
var stmts_end: usize = 0;
for (stmts.items) |stmt| {
for (stmts) |_stmt| {
// zls needs the hint, it seems.
const stmt: Stmt = _stmt;
switch (stmt.data) {
.s_import => |st| {
const record = p.import_records[st.import_record_index];
var record: ImportRecord = p.import_records.items[st.import_record_index];
// The official TypeScript compiler always removes unused imported
// symbols. However, we deliberately deviate from the official
@@ -120,7 +120,7 @@ pub const ImportScanner = struct {
// user is expecting the output to be as small as possible. So we
// should omit unused imports.
//
const keep_unused_imports = !p.options.preserve_unused_imports_ts;
const keep_unused_imports = !p.options.trim_unused_imports;
// TypeScript always trims unused imports. This is important for
// correctness since some imports might be fake (only in the type
@@ -131,10 +131,10 @@ pub const ImportScanner = struct {
if (st.default_name) |default_name| {
found_imports = true;
var symbol = p.symbols.items[default_name.ref.inner_index];
var symbol = p.symbols.items[default_name.ref.?.inner_index];
// TypeScript has a separate definition of unused
if (p.options.ts and p.ts_use_counts.items[default_name.ref.inner_index] != 0) {
if (p.options.ts and p.ts_use_counts.items[default_name.ref.?.inner_index] != 0) {
is_unused_in_typescript = false;
}
@@ -161,7 +161,7 @@ pub const ImportScanner = struct {
var has_any = false;
if (p.import_items_for_namespace.get(st.namespace_ref)) |entry| {
if (entry.size() > 0) {
if (entry.count() > 0) {
has_any = true;
}
}
@@ -171,19 +171,320 @@ pub const ImportScanner = struct {
}
}
}
// Remove items if they are unused
if (st.items.len > 0) {
found_imports = false;
var items_end: usize = 0;
var i: usize = 0;
while (i < st.items.len) : (i += 1) {
const item = st.items[i];
const ref = item.name.ref.?;
const symbol: Symbol = p.symbols.items[ref.inner_index];
// TypeScript has a separate definition of unused
if (p.options.ts and p.ts_use_counts.items[ref.inner_index] != 0) {
is_unused_in_typescript = false;
}
// Remove the symbol if it's never used outside a dead code region
if (symbol.use_count_estimate != 0) {
st.items[items_end] = item;
items_end += 1;
}
}
// Filter the array by taking a slice
if (items_end == 0 and st.items.len > 0) {
p.allocator.free(st.items);
// zero out the slice
st.items = &([_]js_ast.ClauseItem{});
} else if (items_end < st.items.len) {
var list = List(js_ast.ClauseItem).fromOwnedSlice(p.allocator, st.items);
list.shrinkAndFree(items_end);
st.items = list.toOwnedSlice();
}
}
// -- Original Comment --
// Omit this statement if we're parsing TypeScript and all imports are
// unused. Note that this is distinct from the case where there were
// no imports at all (e.g. "import 'foo'"). In that case we want to keep
// the statement because the user is clearly trying to import the module
// for side effects.
//
// This culling is important for correctness when parsing TypeScript
// because a) the TypeScript compiler does ths and we want to match it
// and b) this may be a fake module that only exists in the type system
// and doesn't actually exist in reality.
//
// We do not want to do this culling in JavaScript though because the
// module may have side effects even if all imports are unused.
// -- Original Comment --
// jarred: I think, in this project, we want this behavior, even in JavaScript.
// I think this would be a big performance improvement.
// The less you import, the less code you transpile.
// Side-effect imports are nearly always done through identifier-less imports
// e.g. `import 'fancy-stylesheet-thing/style.css';`
// This is a breaking change though. We can make it an option with some guardrail
// so maybe if it errors, it shows a suggestion "retry without trimming unused imports"
if (found_imports and !p.options.preserve_unused_imports_ts) {
// Ignore import records with a pre-filled source index. These are
// for injected files and we definitely do not want to trim these.
if (!Ref.isSourceIndexNull(record.source_index)) {
record.is_unused = true;
continue;
}
}
}
if (p.options.trim_unused_imports) {
if (st.star_name_loc != null) {
// -- Original Comment --
// If we're bundling a star import and the namespace is only ever
// used for property accesses, then convert each unique property to
// a clause item in the import statement and remove the star import.
// That will cause the bundler to bundle them more efficiently when
// both this module and the imported module are in the same group.
//
// Before:
//
// import * as ns from 'foo'
// console.log(ns.a, ns.b)
//
// After:
//
// import {a, b} from 'foo'
// console.log(a, b)
//
// This is not done if the namespace itself is used, because in that
// case the code for the namespace will have to be generated. This is
// determined by the symbol count because the parser only counts the
// star import as used if it was used for something other than a
// property access:
//
// import * as ns from 'foo'
// console.log(ns, ns.a, ns.b)
//
// -- Original Comment --
// jarred: we don't use the same grouping mechanism as esbuild
// but, we do this anyway.
// The reasons why are:
// * It makes static analysis for other tools simpler.
// * I imagine browsers may someday do some optimizations
// when it's "easier" to know only certain modules are used
// For example, if you're importing a component from a design system
// it's really stupid to import all 1,000 components from that design system
// when you just want <Button />
const namespace_ref = st.namespace_ref;
const convert_star_to_clause = p.symbols.items[namespace_ref.inner_index].use_count_estimate == 0;
if (convert_star_to_clause and !keep_unused_imports) {
st.star_name_loc = null;
}
// "importItemsForNamespace" has property accesses off the namespace
if (p.import_items_for_namespace.get(namespace_ref)) |import_items| {
var count = import_items.count();
if (count > 0) {
// Sort keys for determinism
var sorted: []string = try p.allocator.alloc(string, count);
var iter = import_items.iterator();
var i: usize = 0;
while (iter.next()) |item| {
sorted[i] = item.key;
i += 1;
}
strings.sortAsc(sorted);
if (convert_star_to_clause) {
// Create an import clause for these items. Named imports will be
// automatically created later on since there is now a clause.
var items = try p.allocator.alloc(js_ast.ClauseItem, count);
try p.declared_symbols.ensureUnusedCapacity(count);
i = 0;
for (sorted) |alias| {
const name: LocRef = import_items.get(alias) orelse unreachable;
const original_name = p.symbols.items[name.ref.?.inner_index].original_name;
items[i] = js_ast.ClauseItem{
.alias = alias,
.alias_loc = name.loc,
.name = name,
.original_name = original_name,
};
p.declared_symbols.appendAssumeCapacity(js_ast.DeclaredSymbol{
.ref = name.ref.?,
.is_top_level = true,
});
i += 1;
}
if (st.items.len > 0) {
p.panic("The syntax \"import {{x}}, * as y from 'path'\" isn't valid", .{});
}
st.items = items;
} else {
// If we aren't converting this star import to a clause, still
// create named imports for these property accesses. This will
// cause missing imports to generate useful warnings.
//
// It will also improve bundling efficiency for internal imports
// by still converting property accesses off the namespace into
// bare identifiers even if the namespace is still needed.
for (sorted) |alias| {
const name: LocRef = import_items.get(alias) orelse unreachable;
try p.named_imports.put(name.ref.?, js_ast.NamedImport{
.alias = alias,
.alias_loc = name.loc,
.namespace_ref = st.namespace_ref,
.import_record_index = st.import_record_index,
});
// Make sure the printer prints this as a property access
var symbol: Symbol = p.symbols.items[name.ref.?.inner_index];
symbol.namespace_alias = G.NamespaceAlias{ .namespace_ref = st.namespace_ref, .alias = alias };
p.symbols.items[name.ref.?.inner_index] = symbol;
}
}
}
}
}
}
try p.import_records_for_current_part.append(st.import_record_index);
if (st.star_name_loc != null) {
record.contains_import_star = true;
}
if (st.default_name != null) {
record.contains_default_alias = true;
} else {
for (st.items) |item| {
if (strings.eql(item.alias, "default")) {
record.contains_default_alias = true;
break;
}
}
}
},
.s_function => |st| {
if (st.func.flags.is_export) {
if (st.func.name) |name| {
try p.recordExport(name.loc, p.symbols.items[name.ref.?.inner_index].original_name, name.ref.?);
} else {
try p.log.addRangeError(p.source, logger.Range{ .loc = st.func.open_parens_loc, .len = 2 }, "Exported functions must have a name");
}
}
},
.s_class => |st| {
if (st.is_export) {
if (st.class.class_name) |name| {
try p.recordExport(name.loc, p.symbols.items[name.ref.?.inner_index].original_name, name.ref.?);
} else {
try p.log.addRangeError(p.source, logger.Range{ .loc = st.class.body_loc, .len = 0 }, "Exported classes must have a name");
}
}
},
.s_local => |st| {
if (st.is_export) {
for (st.decls) |decl| {
p.recordExportedBinding(decl.binding);
}
}
// Remove unused import-equals statements, since those likely
// correspond to types instead of values
if (st.was_ts_import_equals and !st.is_export and st.decls.len > 0) {
var decl = st.decls[0];
// Skip to the underlying reference
var value = decl.value;
if (decl.value) |val| {
while (true) {
if (@as(Expr.Tag, val.data) == .e_dot) {
value = val.data.e_dot.target;
} else {
break;
}
}
}
// Is this an identifier reference and not a require() call?
if (value) |val| {
if (@as(Expr.Tag, val.data) == .e_identifier) {
// Is this import statement unused?
if (@as(Binding.Tag, decl.binding.data) == .b_identifier and p.symbols.items[decl.binding.data.b_identifier.ref.inner_index].use_count_estimate == 0) {
p.ignoreUsage(val.data.e_identifier.ref);
scanner.removed_import_equals = true;
continue;
} else {
scanner.kept_import_equals = true;
}
}
}
}
},
.s_export_default => |st| {
try p.recordExport(st.default_name.loc, "default", st.default_name.ref.?);
},
.s_export_clause => |st| {
for (st.items) |item| {
try p.recordExport(item.alias_loc, item.alias, item.name.ref.?);
}
},
.s_export_star => |st| {
try p.import_records_for_current_part.append(st.import_record_index);
if (st.alias) |alias| {
// "export * as ns from 'path'"
try p.named_imports.put(st.namespace_ref, js_ast.NamedImport{
.alias = null,
.alias_is_star = true,
.alias_loc = alias.loc,
.namespace_ref = Ref.None,
.import_record_index = st.import_record_index,
.is_exported = true,
});
try p.recordExport(alias.loc, alias.original_name, st.namespace_ref);
} else {
// "export * from 'path'"
try p.export_star_import_records.append(st.import_record_index);
}
},
.s_export_from => |st| {
try p.import_records_for_current_part.append(st.import_record_index);
for (st.items) |item| {
const ref = item.name.ref orelse p.panic("Expected export from item to have a name {s}", .{st});
// Note that the imported alias is not item.Alias, which is the
// exported alias. This is somewhat confusing because each
// SExportFrom statement is basically SImport + SExportClause in one.
try p.named_imports.put(ref, js_ast.NamedImport{
.alias_is_star = false,
.alias = item.original_name,
.alias_loc = item.name.loc,
.namespace_ref = st.namespace_ref,
.import_record_index = st.import_record_index,
.is_exported = true,
});
try p.recordExport(item.name.loc, item.alias, ref);
}
},
.s_function => |st| {},
.s_class => |st| {},
.s_local => |st| {},
.s_export_default => |st| {},
.s_export_clause => |st| {},
.s_export_star => |st| {},
.s_export_from => |st| {},
else => {},
}
}
stmts[stmts_end] = stmt;
stmts_end += 1;
}
scanner.stmts = stmts[0..stmts_end];
return scanner;
}
};
@@ -968,6 +1269,7 @@ pub const Parser = struct {
use_define_for_class_fields: bool = false,
suppress_warnings_about_weird_code: bool = true,
moduleType: ModuleType = ModuleType.esm,
trim_unused_imports: bool = true,
};
pub fn parse(self: *Parser) !js_ast.Result {
@@ -983,7 +1285,7 @@ pub const Parser = struct {
debugl("<p.parseStmtsUpTo>");
const stmts = try p.parseStmtsUpTo(js_lexer.T.t_end_of_file, &opts);
debugl("</p.parseStmtsUpTo>");
// try p.prepareForVisitPass();
try p.prepareForVisitPass();
// ESM is always strict mode. I don't think we need this.
// // Strip off a leading "use strict" directive when not bundling
@@ -1013,11 +1315,7 @@ pub const Parser = struct {
// Pop the module scope to apply the "ContainsDirectEval" rules
// p.popScope();
debugl("<result.Ast>");
result.ast = js_ast.Ast{
.parts = parts.toOwnedSlice(),
.symbols = p.symbols.toOwnedSlice(),
// .module_scope = p.module_scope.*,
};
result.ast = try p.toAST(parts.toOwnedSlice());
result.ok = true;
debugl("</result.Ast>");
@@ -1402,6 +1700,49 @@ pub const P = struct {
};
}
pub fn recordExportedBinding(p: *P, binding: Binding) void {
switch (binding.data) {
.b_missing => {},
.b_identifier => |ident| {
p.recordExport(binding.loc, p.symbols.items[ident.ref.inner_index].original_name, ident.ref) catch unreachable;
},
.b_array => |array| {
for (array.items) |prop| {
p.recordExportedBinding(prop.binding);
}
},
.b_object => |obj| {
for (obj.properties) |prop| {
p.recordExportedBinding(prop.value);
}
},
else => {
p.panic("Unexpected binding export type {s}", .{binding});
},
}
}
pub fn recordExport(p: *P, loc: logger.Loc, alias: string, ref: Ref) !void {
if (p.named_exports.get(alias)) |name| {
// Duplicate exports are an error
var notes = try p.allocator.alloc(logger.Data, 1);
notes[0] = logger.Data{
.text = try std.fmt.allocPrint(p.allocator, "\"{s}\" was originally exported here", .{alias}),
.location = logger.Location.init_or_nil(p.source, js_lexer.rangeOfIdentifier(&p.source, name.alias_loc)),
};
try p.log.addRangeErrorFmtWithNotes(
p.source,
js_lexer.rangeOfIdentifier(&p.source, loc),
p.allocator,
notes,
"Multiple exports with the same name {s}",
.{alias},
);
} else {
try p.named_exports.put(alias, js_ast.NamedExport{ .alias_loc = loc, .ref = ref });
}
}
pub fn recordUsage(p: *P, ref: *const js_ast.Ref) void {
// The use count stored in the symbol is used for generating symbol names
// during minification. These counts shouldn't include references inside dead
@@ -2160,11 +2501,10 @@ pub const P = struct {
}
pub fn newSymbol(p: *P, kind: Symbol.Kind, identifier: string) !js_ast.Ref {
var ref = js_ast.Ref{
const ref = js_ast.Ref{
.source_index = @intCast(Ref.Int, p.source.index),
.inner_index = @intCast(Ref.Int, p.symbols.items.len),
};
try p.symbols.append(Symbol{
.kind = kind,
.original_name = identifier,
@@ -2611,7 +2951,7 @@ pub const P = struct {
return p.parseStmt(opts);
}
notimpl();
// notimpl();
},
.t_class => {
if (opts.lexical_decl != .allow_all) {
@@ -6652,24 +6992,25 @@ pub const P = struct {
p.relocated_top_level_vars.deinit();
p.relocated_top_level_vars = @TypeOf(p.relocated_top_level_vars).init(p.allocator);
if (partStmts.items.len > 0) {
const _stmts = partStmts.toOwnedSlice();
var part = js_ast.Part{
.stmts = _stmts,
.declared_symbols = p.declared_symbols,
.import_record_indices = p.import_records_for_current_part,
.scopes = p.scopes_for_current_part.toOwnedSlice(),
.can_be_removed_if_unused = p.stmtsCanBeRemovedIfUnused(_stmts),
};
try parts.append(part);
}
// Follow links because "var" declarations may be merged due to hoisting
// while (true) {
// const link = p.symbols.items[local.ref.inner_index].link;
// }
}
if (partStmts.items.len > 0) {
const _stmts = partStmts.toOwnedSlice();
var part = js_ast.Part{
.stmts = _stmts,
.symbol_uses = p.symbol_uses,
.declared_symbols = p.declared_symbols.toOwnedSlice(),
.import_record_indices = p.import_records_for_current_part.toOwnedSlice(),
.scopes = p.scopes_for_current_part.toOwnedSlice(),
.can_be_removed_if_unused = p.stmtsCanBeRemovedIfUnused(_stmts),
};
try parts.append(part);
}
}
pub fn bindingCanBeRemovedIfUnused(p: *P, binding: Binding) bool {
@@ -9463,34 +9804,147 @@ pub const P = struct {
}
}
pub fn toAST(p: *P, _parts: []js_ast.Part) js_ast.Ast {
var parts = std.ArrayList(js_ast.Part).fromOwnedSlice(p.allocator, _parts);
pub fn toAST(p: *P, _parts: []js_ast.Part) !js_ast.Ast {
var parts = _parts;
// Insert an import statement for any runtime imports we generated
if (p.runtime_imports.len > 0 and !p.options.omit_runtime_for_tests) {}
if (p.runtime_imports.count() > 0 and !p.options.omit_runtime_for_tests) {}
var parts_end: usize = 0;
// Handle import paths after the whole file has been visited because we need
// symbol usage counts to be able to remove unused type-only imports in
// TypeScript code.
outer: {
while (true) {
var kept_import_equals = false;
var removed_import_equals = false;
var parts_end: usize = 0;
var i: usize = 0;
// Potentially remove some statements, then filter out parts to remove any
// with no statements
for (parts.items) |part| {
_ = p.import_records_for_current_part.toOwnedSlice()();
while (i < parts.len) : (i += 1) {
var part = parts[i];
_ = p.import_records_for_current_part.toOwnedSlice();
_ = p.declared_symbols.toOwnedSlice();
var result = ImportScanner.scan(p, part.stmts);
var result = try ImportScanner.scan(p, part.stmts);
kept_import_equals = kept_import_equals or result.kept_import_equals;
removed_import_equals = removed_import_equals or result.removed_import_equals;
part.import_record_indices = p.import_records_for_current_part.toOwnedSlice();
part.declared_symbols = p.declared_symbols.toOwnedSlice();
part.stmts = result.stmts;
if (part.stmts.len > 0) {
if (p.module_scope.contains_direct_eval and part.declared_symbols.len > 0) {
// If this file contains a direct call to "eval()", all parts that
// declare top-level symbols must be kept since the eval'd code may
// reference those symbols.
part.can_be_removed_if_unused = false;
}
parts[parts_end] = part;
parts_end += 1;
}
}
break :outer;
// We need to iterate multiple times if an import-equals statement was
// removed and there are more import-equals statements that may be removed
if (!kept_import_equals or !removed_import_equals) {
break;
}
}
parts = parts[0..parts_end];
// Analyze cross-part dependencies for tree shaking and code splitting
{
// Map locals to parts
p.top_level_symbol_to_parts = @TypeOf(p.top_level_symbol_to_parts).init(p.allocator);
var i: usize = 0;
while (i < parts.len) : (i += 1) {
const part = parts[i];
for (part.declared_symbols) |declared| {
if (declared.is_top_level) {
if (p.top_level_symbol_to_parts.contains(declared.ref)) {
try p.top_level_symbol_to_parts.get(declared.ref).?.append(@intCast(u32, i));
} else {
var list = try List(u32).initCapacity(p.allocator, 1);
list.appendAssumeCapacity(@intCast(u32, i));
try p.top_level_symbol_to_parts.put(declared.ref, list);
}
}
}
}
// Each part tracks the other parts it depends on within this file
var local_dependencies = std.AutoHashMap(u32, u32).init(p.allocator);
i = 0;
while (i < parts.len) : (i += 1) {
const part = parts[i];
var iter = part.symbol_uses.iterator();
var dependencies = List(js_ast.Dependency).init(p.allocator);
while (iter.next()) |entry| {
const ref = entry.key;
if (p.top_level_symbol_to_parts.get(ref)) |tlstp| {
for (tlstp.items) |other_part_index| {
if (!local_dependencies.contains(other_part_index) or other_part_index != i) {
try local_dependencies.put(other_part_index, @intCast(u32, i));
try dependencies.append(js_ast.Dependency{
.source_index = p.source.index,
.part_index = other_part_index,
});
}
}
}
// Also map from imports to parts that use them
// TODO: will appending to this list like this be a perf issue?
if (p.named_imports.getEntry(ref)) |named_import_entry| {
const named_import = named_import_entry.value;
var buf = try p.allocator.alloc(u32, named_import.local_parts_with_uses.len + 1);
if (named_import.local_parts_with_uses.len > 0) {
std.mem.copy(u32, buf, named_import.local_parts_with_uses);
}
buf[buf.len - 1] = @intCast(u32, i);
named_import_entry.value.local_parts_with_uses = buf;
}
}
}
}
var exports_kind = js_ast.ExportsKind.none;
const uses_exports_ref = p.symbols.items[p.exports_ref.inner_index].use_count_estimate > 0;
const uses_module_ref = p.symbols.items[p.module_ref.inner_index].use_count_estimate > 0;
if (p.es6_export_keyword.len > 0 or p.top_level_await_keyword.len > 0) {
exports_kind = .esm;
} else if (uses_exports_ref or uses_module_ref or p.has_top_level_return) {
exports_kind = .cjs;
} else {
exports_kind = .esm;
}
var wrapper_name = try p.allocator.alloc(u8, "require_".len + p.source.identifier_name.len);
std.mem.copy(u8, wrapper_name[0.."require_".len], "require_");
std.mem.copy(u8, wrapper_name["require_".len..wrapper_name.len], p.source.identifier_name);
var wrapper = try p.newSymbol(.other, wrapper_name);
return js_ast.Ast{
.parts = parts,
.module_scope = p.module_scope.*,
.symbols = p.symbols.toOwnedSlice(),
.exports_ref = p.exports_ref,
.wrapper_ref = wrapper,
.import_records = p.import_records.toOwnedSlice(),
.export_star_import_records = p.export_star_import_records.toOwnedSlice(),
.top_level_symbol_to_parts = p.top_level_symbol_to_parts,
.approximate_line_count = p.lexer.approximate_newline_count + 1,
.exports_kind = exports_kind,
.named_imports = p.named_imports,
.named_exports = p.named_exports,
.import_keyword = p.es6_import_keyword,
.export_keyword = p.es6_export_keyword,
// .top_Level_await_keyword = p.top_level_await_keyword,
};
}
pub fn init(allocator: *std.mem.Allocator, log: *logger.Log, source: logger.Source, define: *Define, lexer: js_lexer.Lexer, opts: Parser.Options) !*P {
@@ -9507,6 +9961,7 @@ pub const P = struct {
parser.export_star_import_records = @TypeOf(parser.export_star_import_records).init(allocator);
parser.import_items_for_namespace = @TypeOf(parser.import_items_for_namespace).init(allocator);
parser.named_imports = @TypeOf(parser.named_imports).init(allocator);
parser.named_exports = @TypeOf(parser.named_exports).init(allocator);
parser.top_level_symbol_to_parts = @TypeOf(parser.top_level_symbol_to_parts).init(allocator);
parser.import_namespace_cc_map = @TypeOf(parser.import_namespace_cc_map).init(allocator);
parser.scopes_in_order = @TypeOf(parser.scopes_in_order).init(allocator);
@@ -9514,6 +9969,7 @@ pub const P = struct {
parser.relocated_top_level_vars = @TypeOf(parser.relocated_top_level_vars).init(allocator);
parser.log = log;
parser.allocator = allocator;
parser.runtime_imports = StringRefMap.init(allocator);
parser.options = opts;
parser.to_expr_wrapper_namespace = Binding2ExprWrapper.Namespace.init(parser);
parser.to_expr_wrapper_hoisted = Binding2ExprWrapper.Hoisted.init(parser);

View File

@@ -187,6 +187,15 @@ pub const Log = struct {
});
}
pub fn addRangeErrorFmtWithNotes(log: *Log, source: ?Source, r: Range, allocator: *std.mem.Allocator, notes: []Data, comptime text: string, args: anytype) !void {
log.errors += 1;
try log.addMsg(Msg{
.kind = .err,
.data = rangeData(source, r, std.fmt.allocPrint(allocator, text, args) catch unreachable),
.notes = notes,
});
}
pub fn addErrorFmt(log: *Log, source: ?Source, l: Loc, allocator: *std.mem.Allocator, comptime text: string, args: anytype) !void {
log.errors += 1;
try log.addMsg(Msg{

View File

@@ -13,6 +13,7 @@ usingnamespace @import("defines.zig");
pub fn main() anyerror!void {
try alloc.setup(std.heap.page_allocator);
const args = try std.process.argsAlloc(alloc.dynamic);
const stdout = std.io.getStdOut();
const stderr = std.io.getStdErr();
@@ -73,9 +74,19 @@ pub fn main() anyerror!void {
ast,
js_ast.Symbol.Map.initList(symbols),
false,
js_printer.Options{ .to_module_ref = js_ast.Ref{ .inner_index = 0 } },
js_printer.Options{ .to_module_ref = ast.module_ref orelse js_ast.Ref{ .inner_index = 0 } },
&_linker,
);
if (std.builtin.mode == std.builtin.Mode.Debug) {
std.debug.print("\n--AST DEBUG--:\n", .{});
std.debug.print("Lines: {d}\n", .{ast.approximate_line_count});
std.debug.print("Parts: {d}\n{s}\n", .{ ast.parts.len, ast.parts });
std.debug.print("Symbols: {d}\n{s}\n", .{ ast.symbols.len, ast.symbols });
std.debug.print("Imports: {d}\n{s}\n", .{ ast.named_exports.count(), ast.named_imports });
std.debug.print("Exports: {d}\n{s}\n", .{ ast.named_imports.count(), ast.named_exports });
std.debug.print("\n--AST DEBUG--:\n", .{});
}
_ = try stdout.write(printed.js);
}

View File

@@ -239,6 +239,55 @@ pub fn containsNonBmpCodePointUTF16(_text: JavascriptString) bool {
return false;
}
pub fn join(slices: []const string, delimiter: string, allocator: *std.mem.Allocator) !string {
return try std.mem.join(allocator, delimiter, slices);
}
pub fn cmpStringsAsc(ctx: void, a: string, b: string) bool {
return std.mem.order(u8, a, b) == .lt;
}
pub fn cmpStringsDesc(ctx: void, a: string, b: string) bool {
return std.mem.order(u8, a, b) == .gt;
}
const sort_asc = std.sort.asc(u8);
const sort_desc = std.sort.desc(u8);
pub fn sortAsc(in: []string) void {
std.sort.sort([]const u8, in, {}, cmpStringsAsc);
}
pub fn sortDesc(in: []string) void {
std.sort.sort([]const u8, in, {}, cmpStringsDesc);
}
test "join" {
var string_list = &[_]string{ "abc", "def", "123", "hello" };
const list = try join(string_list, "-", std.heap.page_allocator);
std.testing.expectEqualStrings("abc-def-123-hello", list);
}
test "sortAsc" {
var string_list = [_]string{ "abc", "def", "123", "hello" };
var sorted_string_list = [_]string{ "123", "abc", "def", "hello" };
var sorted_join = try join(&sorted_string_list, "-", std.heap.page_allocator);
sortAsc(&string_list);
var string_join = try join(&string_list, "-", std.heap.page_allocator);
std.testing.expectEqualStrings(sorted_join, string_join);
}
test "sortDesc" {
var string_list = [_]string{ "abc", "def", "123", "hello" };
var sorted_string_list = [_]string{ "hello", "def", "abc", "123" };
var sorted_join = try join(&sorted_string_list, "-", std.heap.page_allocator);
sortDesc(&string_list);
var string_join = try join(&string_list, "-", std.heap.page_allocator);
std.testing.expectEqualStrings(sorted_join, string_join);
}
/// Super simple "perfect hash" algorithm
/// Only really useful for switching on strings
// TODO: can we auto detect and promote the underlying type?