868 lines
30 KiB
Go
868 lines
30 KiB
Go
package estransforms
|
|
|
|
import (
|
|
"maps"
|
|
"slices"
|
|
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/printer"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/transformers"
|
|
)
|
|
|
|
type usingDeclarationTransformer struct {
|
|
transformers.Transformer
|
|
|
|
exportBindings map[string]*ast.ExportSpecifierNode
|
|
exportVars []*ast.VariableDeclarationNode
|
|
defaultExportBinding *ast.IdentifierNode
|
|
exportEqualsBinding *ast.IdentifierNode
|
|
}
|
|
|
|
func newUsingDeclarationTransformer(opts *transformers.TransformOptions) *transformers.Transformer {
|
|
tx := &usingDeclarationTransformer{}
|
|
return tx.NewTransformer(tx.visit, opts.Context)
|
|
}
|
|
|
|
type usingKind uint
|
|
|
|
const (
|
|
usingKindNone usingKind = iota
|
|
usingKindSync
|
|
usingKindAsync
|
|
)
|
|
|
|
func (tx *usingDeclarationTransformer) visit(node *ast.Node) *ast.Node {
|
|
if node.SubtreeFacts()&ast.SubtreeContainsUsing == 0 {
|
|
return node
|
|
}
|
|
|
|
switch node.Kind {
|
|
case ast.KindSourceFile:
|
|
node = tx.visitSourceFile(node.AsSourceFile())
|
|
case ast.KindBlock:
|
|
node = tx.visitBlock(node.AsBlock())
|
|
case ast.KindForStatement:
|
|
node = tx.visitForStatement(node.AsForStatement())
|
|
case ast.KindForOfStatement:
|
|
node = tx.visitForOfStatement(node.AsForInOrOfStatement())
|
|
case ast.KindSwitchStatement:
|
|
node = tx.visitSwitchStatement(node.AsSwitchStatement())
|
|
default:
|
|
node = tx.Visitor().VisitEachChild(node)
|
|
}
|
|
return node
|
|
}
|
|
|
|
func (tx *usingDeclarationTransformer) visitSourceFile(node *ast.SourceFile) *ast.Node {
|
|
if node.IsDeclarationFile {
|
|
return node.AsNode()
|
|
}
|
|
|
|
var visited *ast.SourceFileNode
|
|
usingKind := getUsingKindOfStatements(node.Statements.Nodes)
|
|
if usingKind != usingKindNone {
|
|
// Imports and exports must stay at the top level. This means we must hoist all imports, exports, and
|
|
// top-level function declarations and bindings out of the `try` statements we generate. For example:
|
|
//
|
|
// given:
|
|
//
|
|
// import { w } from "mod";
|
|
// const x = expr1;
|
|
// using y = expr2;
|
|
// const z = expr3;
|
|
// export function f() {
|
|
// console.log(z);
|
|
// }
|
|
//
|
|
// produces:
|
|
//
|
|
// import { x } from "mod"; // <-- preserved
|
|
// const x = expr1; // <-- preserved
|
|
// var y, z; // <-- hoisted
|
|
// export function f() { // <-- hoisted
|
|
// console.log(z);
|
|
// }
|
|
// const env_1 = { stack: [], error: void 0, hasError: false };
|
|
// try {
|
|
// y = __addDisposableResource(env_1, expr2, false);
|
|
// z = expr3;
|
|
// }
|
|
// catch (e_1) {
|
|
// env_1.error = e_1;
|
|
// env_1.hasError = true;
|
|
// }
|
|
// finally {
|
|
// __disposeResource(env_1);
|
|
// }
|
|
//
|
|
// In this transformation, we hoist `y`, `z`, and `f` to a new outer statement list while moving all other
|
|
// statements in the source file into the `try` block, which is the same approach we use for System module
|
|
// emit. Unlike System module emit, we attempt to preserve all statements prior to the first top-level
|
|
// `using` to isolate the complexity of the transformed output to only where it is necessary.
|
|
tx.EmitContext().StartVariableEnvironment()
|
|
|
|
tx.exportBindings = make(map[string]*ast.ExportSpecifierNode)
|
|
tx.exportVars = nil
|
|
|
|
prologue, rest := tx.Factory().SplitStandardPrologue(node.Statements.Nodes)
|
|
var topLevelStatements []*ast.Statement
|
|
topLevelStatements = append(topLevelStatements, core.FirstResult(tx.Visitor().VisitSlice(prologue))...)
|
|
|
|
// Collect and transform any leading statements up to the first `using` or `await using`. This preserves
|
|
// the original statement order much as is possible.
|
|
|
|
pos := 0
|
|
for pos < len(rest) {
|
|
statement := rest[pos]
|
|
if getUsingKind(statement) != usingKindNone {
|
|
if pos > 0 {
|
|
topLevelStatements = append(topLevelStatements, core.FirstResult(tx.Visitor().VisitSlice(rest[:pos]))...)
|
|
}
|
|
break
|
|
}
|
|
pos++
|
|
}
|
|
|
|
if pos >= len(rest) {
|
|
panic("Should have encountered at least one 'using' statement.")
|
|
}
|
|
|
|
// transform the rest of the body
|
|
envBinding := tx.createEnvBinding()
|
|
bodyStatements := tx.transformUsingDeclarations(rest[pos:], envBinding, &topLevelStatements)
|
|
|
|
// add `export {}` declarations for any hoisted bindings.
|
|
if len(tx.exportBindings) > 0 {
|
|
topLevelStatements = append(
|
|
topLevelStatements,
|
|
tx.Factory().NewExportDeclaration(
|
|
nil, /*modifiers*/
|
|
false, /*isTypeOnly*/
|
|
tx.Factory().NewNamedExports(
|
|
tx.Factory().NewNodeList(
|
|
slices.Collect(maps.Values(tx.exportBindings)),
|
|
),
|
|
),
|
|
nil, /*moduleSpecifier*/
|
|
nil, /*attributes*/
|
|
),
|
|
)
|
|
}
|
|
|
|
topLevelStatements = tx.EmitContext().EndAndMergeVariableEnvironment(topLevelStatements)
|
|
if len(tx.exportVars) > 0 {
|
|
topLevelStatements = append(topLevelStatements, tx.Factory().NewVariableStatement(
|
|
tx.Factory().NewModifierList([]*ast.Node{
|
|
tx.Factory().NewModifier(ast.KindExportKeyword),
|
|
}),
|
|
tx.Factory().NewVariableDeclarationList(
|
|
ast.NodeFlagsLet,
|
|
tx.Factory().NewNodeList(tx.exportVars),
|
|
),
|
|
))
|
|
}
|
|
topLevelStatements = append(topLevelStatements, tx.createDownlevelUsingStatements(bodyStatements, envBinding, usingKind == usingKindAsync)...)
|
|
|
|
if tx.exportEqualsBinding != nil {
|
|
topLevelStatements = append(topLevelStatements, tx.Factory().NewExportAssignment(
|
|
nil, /*modifiers*/
|
|
true, /*isExportEquals*/
|
|
nil, /*typeNode*/
|
|
tx.exportEqualsBinding,
|
|
))
|
|
}
|
|
|
|
visited = tx.Factory().UpdateSourceFile(node, tx.Factory().NewNodeList(topLevelStatements), node.EndOfFileToken)
|
|
} else {
|
|
visited = tx.Visitor().VisitEachChild(node.AsNode())
|
|
}
|
|
tx.EmitContext().AddEmitHelper(visited, tx.EmitContext().ReadEmitHelpers()...)
|
|
tx.exportVars = nil
|
|
tx.exportBindings = nil
|
|
tx.defaultExportBinding = nil
|
|
tx.exportEqualsBinding = nil
|
|
return visited
|
|
}
|
|
|
|
func (tx *usingDeclarationTransformer) visitBlock(node *ast.Block) *ast.Node {
|
|
usingKind := getUsingKindOfStatements(node.Statements.Nodes)
|
|
if usingKind != usingKindNone {
|
|
prologue, rest := tx.Factory().SplitStandardPrologue(node.Statements.Nodes)
|
|
envBinding := tx.createEnvBinding()
|
|
statements := make([]*ast.Statement, 0, len(prologue)+2)
|
|
statements = append(statements, core.FirstResult(tx.Visitor().VisitSlice(prologue))...)
|
|
statements = append(statements, tx.createDownlevelUsingStatements(
|
|
tx.transformUsingDeclarations(rest, envBinding, nil /*topLevelStatements*/),
|
|
envBinding,
|
|
usingKind == usingKindAsync,
|
|
)...)
|
|
statementList := tx.Factory().NewNodeList(statements)
|
|
statementList.Loc = node.Statements.Loc
|
|
return tx.Factory().UpdateBlock(node, statementList)
|
|
}
|
|
return tx.Visitor().VisitEachChild(node.AsNode())
|
|
}
|
|
|
|
func (tx *usingDeclarationTransformer) visitForStatement(node *ast.ForStatement) *ast.Node {
|
|
if node.Initializer != nil && isUsingVariableDeclarationList(node.Initializer) {
|
|
// given:
|
|
//
|
|
// for (using x = expr; cond; incr) { ... }
|
|
//
|
|
// produces a shallow transformation to:
|
|
//
|
|
// {
|
|
// using x = expr;
|
|
// for (; cond; incr) { ... }
|
|
// }
|
|
//
|
|
// before handing the shallow transformation back to the visitor for an in-depth transformation.
|
|
return tx.Visitor().VisitNode(
|
|
tx.Factory().NewBlock(tx.Factory().NewNodeList([]*ast.Statement{
|
|
tx.Factory().NewVariableStatement(nil /*modifiers*/, node.Initializer),
|
|
tx.Factory().UpdateForStatement(
|
|
node,
|
|
nil, /*initializer*/
|
|
node.Condition,
|
|
node.Incrementor,
|
|
node.Statement,
|
|
),
|
|
}), false /*multiLine*/),
|
|
)
|
|
}
|
|
return tx.Visitor().VisitEachChild(node.AsNode())
|
|
}
|
|
|
|
func (tx *usingDeclarationTransformer) visitForOfStatement(node *ast.ForInOrOfStatement) *ast.Node {
|
|
if isUsingVariableDeclarationList(node.Initializer) {
|
|
// given:
|
|
//
|
|
// for (using x of y) { ... }
|
|
//
|
|
// produces a shallow transformation to:
|
|
//
|
|
// for (const x_1 of y) {
|
|
// using x = x;
|
|
// ...
|
|
// }
|
|
//
|
|
// before handing the shallow transformation back to the visitor for an in-depth transformation.
|
|
forInitializer := node.Initializer.AsVariableDeclarationList()
|
|
forDecl := core.FirstOrNil(forInitializer.Declarations.Nodes)
|
|
if forDecl == nil {
|
|
forDecl = tx.Factory().NewVariableDeclaration(tx.Factory().NewTempVariable(), nil, nil, nil)
|
|
}
|
|
|
|
isAwaitUsing := getUsingKindOfVariableDeclarationList(forInitializer) == usingKindAsync
|
|
temp := tx.Factory().NewGeneratedNameForNode(forDecl.Name())
|
|
usingVar := tx.Factory().UpdateVariableDeclaration(forDecl.AsVariableDeclaration(), forDecl.Name(), nil /*exclamationToken*/, nil /*type*/, temp)
|
|
usingVarList := tx.Factory().NewVariableDeclarationList(
|
|
core.IfElse(isAwaitUsing, ast.NodeFlagsAwaitUsing, ast.NodeFlagsUsing),
|
|
tx.Factory().NewNodeList([]*ast.Node{usingVar}),
|
|
)
|
|
usingVarStatement := tx.Factory().NewVariableStatement(nil /*modifiers*/, usingVarList)
|
|
var statement *ast.Statement
|
|
if ast.IsBlock(node.Statement) {
|
|
statements := make([]*ast.Statement, 0, len(node.Statement.AsBlock().Statements.Nodes)+1)
|
|
statements = append(statements, usingVarStatement)
|
|
statements = append(statements, node.Statement.AsBlock().Statements.Nodes...)
|
|
statement = tx.Factory().UpdateBlock(
|
|
node.Statement.AsBlock(),
|
|
tx.Factory().NewNodeList(statements),
|
|
)
|
|
} else {
|
|
statement = tx.Factory().NewBlock(
|
|
tx.Factory().NewNodeList([]*ast.Statement{
|
|
usingVarStatement,
|
|
node.Statement,
|
|
}),
|
|
true, /*multiLine*/
|
|
)
|
|
}
|
|
return tx.Visitor().VisitNode(
|
|
tx.Factory().UpdateForInOrOfStatement(
|
|
node,
|
|
node.AwaitModifier,
|
|
tx.Factory().NewVariableDeclarationList(
|
|
ast.NodeFlagsConst,
|
|
tx.Factory().NewNodeList([]*ast.VariableDeclarationNode{
|
|
tx.Factory().NewVariableDeclaration(temp, nil /*exclamationToken*/, nil /*type*/, nil),
|
|
}),
|
|
),
|
|
node.Expression,
|
|
statement,
|
|
),
|
|
)
|
|
}
|
|
return tx.Visitor().VisitEachChild(node.AsNode())
|
|
}
|
|
|
|
func (tx *usingDeclarationTransformer) visitCaseOrDefaultClause(node *ast.CaseOrDefaultClause, envBinding *ast.IdentifierNode) *ast.Node {
|
|
if getUsingKindOfStatements(node.Statements.Nodes) != usingKindNone {
|
|
return tx.Factory().UpdateCaseOrDefaultClause(
|
|
node,
|
|
tx.Visitor().VisitNode(node.Expression),
|
|
tx.Factory().NewNodeList(tx.transformUsingDeclarations(node.Statements.Nodes, envBinding, nil /*topLevelStatements*/)),
|
|
)
|
|
}
|
|
return tx.Visitor().VisitEachChild(node.AsNode())
|
|
}
|
|
|
|
func (tx *usingDeclarationTransformer) visitSwitchStatement(node *ast.SwitchStatement) *ast.Node {
|
|
// given:
|
|
//
|
|
// switch (expr) {
|
|
// case expr:
|
|
// using res = expr;
|
|
// }
|
|
//
|
|
// produces:
|
|
//
|
|
// const env_1 = { stack: [], error: void 0, hasError: false };
|
|
// try {
|
|
// switch(expr) {
|
|
// case expr:
|
|
// const res = __addDisposableResource(env_1, expr, false);
|
|
// }
|
|
// }
|
|
// catch (e_1) {
|
|
// env_1.error = e_1;
|
|
// env_1.hasError = true;
|
|
// }
|
|
// finally {
|
|
// __disposeResources(env_1);
|
|
// }
|
|
//
|
|
usingKind := getUsingKindOfCaseOrDefaultClauses(node.CaseBlock.AsCaseBlock().Clauses.Nodes)
|
|
if usingKind != usingKindNone {
|
|
envBinding := tx.createEnvBinding()
|
|
return transformers.SingleOrMany(tx.createDownlevelUsingStatements(
|
|
[]*ast.Statement{
|
|
tx.Factory().UpdateSwitchStatement(
|
|
node,
|
|
tx.Visitor().VisitNode(node.Expression),
|
|
tx.Factory().UpdateCaseBlock(
|
|
node.CaseBlock.AsCaseBlock(),
|
|
tx.Factory().NewNodeList(
|
|
core.Map(node.CaseBlock.AsCaseBlock().Clauses.Nodes, func(clause *ast.CaseOrDefaultClauseNode) *ast.CaseOrDefaultClauseNode {
|
|
return tx.visitCaseOrDefaultClause(clause.AsCaseOrDefaultClause(), envBinding)
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
},
|
|
envBinding,
|
|
usingKind == usingKindAsync,
|
|
), tx.Factory())
|
|
}
|
|
|
|
return tx.Visitor().VisitEachChild(node.AsNode())
|
|
}
|
|
|
|
func (tx *usingDeclarationTransformer) transformUsingDeclarations(statementsIn []*ast.Statement, envBinding *ast.IdentifierNode, topLevelStatements *[]*ast.Statement) []*ast.Node {
|
|
var statements []*ast.Statement
|
|
|
|
hoist := func(node *ast.Statement) *ast.Statement {
|
|
if topLevelStatements == nil {
|
|
return node
|
|
}
|
|
|
|
switch node.Kind {
|
|
case ast.KindImportDeclaration,
|
|
ast.KindImportEqualsDeclaration,
|
|
ast.KindExportDeclaration,
|
|
ast.KindFunctionDeclaration:
|
|
tx.hoistImportOrExportOrHoistedDeclaration(node, topLevelStatements)
|
|
return nil
|
|
case ast.KindExportAssignment:
|
|
return tx.hoistExportAssignment(node.AsExportAssignment())
|
|
case ast.KindClassDeclaration:
|
|
return tx.hoistClassDeclaration(node.AsClassDeclaration())
|
|
case ast.KindVariableStatement:
|
|
return tx.hoistVariableStatement(node.AsVariableStatement())
|
|
}
|
|
|
|
return node
|
|
}
|
|
|
|
hoistOrAppendNode := func(node *ast.Node) {
|
|
node = hoist(node)
|
|
if node != nil {
|
|
statements = append(statements, node)
|
|
}
|
|
}
|
|
|
|
for _, statement := range statementsIn {
|
|
usingKind := getUsingKind(statement)
|
|
if usingKind != usingKindNone {
|
|
varStatement := statement.AsVariableStatement()
|
|
declarationList := varStatement.DeclarationList
|
|
var declarations []*ast.VariableDeclarationNode
|
|
for _, declaration := range declarationList.AsVariableDeclarationList().Declarations.Nodes {
|
|
if !ast.IsIdentifier(declaration.Name()) {
|
|
// Since binding patterns are a grammar error, we reset `declarations` so we don't process this as a `using`.
|
|
declarations = nil
|
|
break
|
|
}
|
|
|
|
// perform a shallow transform for any named evaluation
|
|
if isNamedEvaluation(tx.EmitContext(), declaration) {
|
|
declaration = transformNamedEvaluation(tx.EmitContext(), declaration, false /*ignoreEmptyStringLiteral*/, "" /*assignedName*/)
|
|
}
|
|
|
|
initializer := tx.Visitor().VisitNode(declaration.Initializer())
|
|
if initializer == nil {
|
|
initializer = tx.Factory().NewVoidZeroExpression()
|
|
}
|
|
declarations = append(declarations, tx.Factory().UpdateVariableDeclaration(
|
|
declaration.AsVariableDeclaration(),
|
|
declaration.Name(),
|
|
nil, /*exclamationToken*/
|
|
nil, /*type*/
|
|
tx.Factory().NewAddDisposableResourceHelper(
|
|
envBinding,
|
|
initializer,
|
|
usingKind == usingKindAsync,
|
|
),
|
|
))
|
|
}
|
|
|
|
// Only replace the statement if it was valid.
|
|
if len(declarations) > 0 {
|
|
varList := tx.Factory().NewVariableDeclarationList(ast.NodeFlagsConst, tx.Factory().NewNodeList(declarations))
|
|
tx.EmitContext().SetOriginal(varList, declarationList)
|
|
varList.Loc = declarationList.Loc
|
|
hoistOrAppendNode(tx.Factory().UpdateVariableStatement(varStatement, nil /*modifiers*/, varList))
|
|
continue
|
|
}
|
|
}
|
|
|
|
if result := tx.visit(statement); result != nil {
|
|
if result.Kind == ast.KindSyntaxList {
|
|
for _, node := range result.AsSyntaxList().Children {
|
|
hoistOrAppendNode(node)
|
|
}
|
|
} else {
|
|
hoistOrAppendNode(result)
|
|
}
|
|
}
|
|
}
|
|
return statements
|
|
}
|
|
|
|
func (tx *usingDeclarationTransformer) hoistImportOrExportOrHoistedDeclaration(node *ast.Statement, topLevelStatements *[]*ast.Statement) {
|
|
// NOTE: `node` has already been visited
|
|
*topLevelStatements = append(*topLevelStatements, node)
|
|
}
|
|
|
|
func (tx *usingDeclarationTransformer) hoistExportAssignment(node *ast.ExportAssignment) *ast.Statement {
|
|
if node.IsExportEquals {
|
|
return tx.hoistExportEquals(node)
|
|
} else {
|
|
return tx.hoistExportDefault(node)
|
|
}
|
|
}
|
|
|
|
func (tx *usingDeclarationTransformer) hoistExportDefault(node *ast.ExportAssignment) *ast.Statement {
|
|
// NOTE: `node` has already been visited
|
|
if tx.defaultExportBinding != nil {
|
|
// invalid case of multiple `export default` declarations. Don't assert here, just pass it through
|
|
return node.AsNode()
|
|
}
|
|
|
|
// given:
|
|
//
|
|
// export default expr;
|
|
//
|
|
// produces:
|
|
//
|
|
// // top level
|
|
// var default_1;
|
|
// export { default_1 as default };
|
|
//
|
|
// // body
|
|
// default_1 = expr;
|
|
|
|
tx.defaultExportBinding = tx.Factory().NewUniqueNameEx("_default", printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsReservedInNestedScopes | printer.GeneratedIdentifierFlagsFileLevel | printer.GeneratedIdentifierFlagsOptimistic})
|
|
tx.hoistBindingIdentifier(tx.defaultExportBinding /*isExport*/, true, tx.Factory().NewIdentifier("default"), node.AsNode())
|
|
|
|
// give a class or function expression an assigned name, if needed.
|
|
expression := node.Expression
|
|
innerExpression := ast.SkipOuterExpressions(expression, ast.OEKAll)
|
|
if isNamedEvaluation(tx.EmitContext(), innerExpression) {
|
|
innerExpression = transformNamedEvaluation(tx.EmitContext(), innerExpression /*ignoreEmptyStringLiteral*/, false, "default")
|
|
expression = tx.Factory().RestoreOuterExpressions(expression, innerExpression, ast.OEKAll)
|
|
}
|
|
|
|
assignment := tx.Factory().NewAssignmentExpression(tx.defaultExportBinding, expression)
|
|
return tx.Factory().NewExpressionStatement(assignment)
|
|
}
|
|
|
|
func (tx *usingDeclarationTransformer) hoistExportEquals(node *ast.ExportAssignment) *ast.Statement {
|
|
// NOTE: `node` has already been visited
|
|
if tx.exportEqualsBinding != nil {
|
|
// invalid case of multiple `export default` declarations. Don't assert here, just pass it through
|
|
return node.AsNode()
|
|
}
|
|
|
|
// given:
|
|
//
|
|
// export = expr;
|
|
//
|
|
// produces:
|
|
//
|
|
// // top level
|
|
// var default_1;
|
|
//
|
|
// try {
|
|
// // body
|
|
// default_1 = expr;
|
|
// } ...
|
|
//
|
|
// // top level suffix
|
|
// export = default_1;
|
|
|
|
tx.exportEqualsBinding = tx.Factory().NewUniqueNameEx("_default", printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsReservedInNestedScopes | printer.GeneratedIdentifierFlagsFileLevel | printer.GeneratedIdentifierFlagsOptimistic})
|
|
tx.EmitContext().AddVariableDeclaration(tx.exportEqualsBinding)
|
|
|
|
// give a class or function expression an assigned name, if needed.
|
|
assignment := tx.Factory().NewAssignmentExpression(tx.exportEqualsBinding, node.Expression)
|
|
return tx.Factory().NewExpressionStatement(assignment)
|
|
}
|
|
|
|
func (tx *usingDeclarationTransformer) hoistClassDeclaration(node *ast.ClassDeclaration) *ast.Statement {
|
|
// NOTE: `node` has already been visited
|
|
if node.Name() == nil && tx.defaultExportBinding != nil {
|
|
// invalid case of multiple `export default` declarations. Don't assert here, just pass it through
|
|
return node.AsNode()
|
|
}
|
|
|
|
isExported := ast.HasSyntacticModifier(node.AsNode(), ast.ModifierFlagsExport)
|
|
isDefault := ast.HasSyntacticModifier(node.AsNode(), ast.ModifierFlagsDefault)
|
|
|
|
// When hoisting a class declaration at the top level of a file containing a top-level `using` statement, we
|
|
// must first convert it to a class expression so that we can hoist the binding outside of the `try`.
|
|
expression := convertClassDeclarationToClassExpression(tx.EmitContext(), node)
|
|
if node.Name() != nil {
|
|
// given:
|
|
//
|
|
// using x = expr;
|
|
// class C {}
|
|
//
|
|
// produces:
|
|
//
|
|
// var x, C;
|
|
// const env_1 = { ... };
|
|
// try {
|
|
// x = __addDisposableResource(env_1, expr, false);
|
|
// C = class {};
|
|
// }
|
|
// catch (e_1) {
|
|
// env_1.error = e_1;
|
|
// env_1.hasError = true;
|
|
// }
|
|
// finally {
|
|
// __disposeResources(env_1);
|
|
// }
|
|
//
|
|
// If the class is exported, we also produce an `export { C };`
|
|
tx.hoistBindingIdentifier(tx.Factory().GetLocalName(node.AsNode()), isExported && !isDefault, nil /*exportAlias*/, node.AsNode())
|
|
expression = tx.Factory().NewAssignmentExpression(tx.Factory().GetDeclarationName(node.AsNode()), expression)
|
|
tx.EmitContext().SetOriginal(expression, node.AsNode())
|
|
tx.EmitContext().SetSourceMapRange(expression, node.Loc)
|
|
tx.EmitContext().SetCommentRange(expression, node.Loc)
|
|
if isNamedEvaluation(tx.EmitContext(), expression) {
|
|
expression = transformNamedEvaluation(tx.EmitContext(), expression, false /*ignoreEmptyStringLiteral*/, "" /*assignedName*/)
|
|
}
|
|
}
|
|
|
|
if isDefault && tx.defaultExportBinding == nil {
|
|
// In the case of a default export, we create a temporary variable that we export as the default and then
|
|
// assign to that variable.
|
|
//
|
|
// given:
|
|
//
|
|
// using x = expr;
|
|
// export default class C {}
|
|
//
|
|
// produces:
|
|
//
|
|
// export { default_1 as default };
|
|
// var x, C, default_1;
|
|
// const env_1 = { ... };
|
|
// try {
|
|
// x = __addDisposableResource(env_1, expr, false);
|
|
// default_1 = C = class {};
|
|
// }
|
|
// catch (e_1) {
|
|
// env_1.error = e_1;
|
|
// env_1.hasError = true;
|
|
// }
|
|
// finally {
|
|
// __disposeResources(env_1);
|
|
// }
|
|
//
|
|
// Though we will never reassign `default_1`, this most closely matches the specified runtime semantics.
|
|
tx.defaultExportBinding = tx.Factory().NewUniqueNameEx("_default", printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsReservedInNestedScopes | printer.GeneratedIdentifierFlagsFileLevel | printer.GeneratedIdentifierFlagsOptimistic})
|
|
tx.hoistBindingIdentifier(tx.defaultExportBinding /*isExport*/, true, tx.Factory().NewIdentifier("default"), node.AsNode())
|
|
expression = tx.Factory().NewAssignmentExpression(tx.defaultExportBinding, expression)
|
|
tx.EmitContext().SetOriginal(expression, node.AsNode())
|
|
if isNamedEvaluation(tx.EmitContext(), expression) {
|
|
expression = transformNamedEvaluation(tx.EmitContext(), expression /*ignoreEmptyStringLiteral*/, false, "default")
|
|
}
|
|
}
|
|
|
|
return tx.Factory().NewExpressionStatement(expression)
|
|
}
|
|
|
|
func (tx *usingDeclarationTransformer) hoistVariableStatement(node *ast.VariableStatement) *ast.Statement {
|
|
// NOTE: `node` has already been visited
|
|
var expressions []*ast.Expression
|
|
isExported := ast.HasSyntacticModifier(node.AsNode(), ast.ModifierFlagsExport)
|
|
for _, variable := range node.DeclarationList.AsVariableDeclarationList().Declarations.Nodes {
|
|
tx.hoistBindingElement(variable, isExported, variable)
|
|
if variable.Initializer() != nil {
|
|
expressions = append(expressions, tx.hoistInitializedVariable(variable.AsVariableDeclaration()))
|
|
}
|
|
}
|
|
if len(expressions) > 0 {
|
|
statement := tx.Factory().NewExpressionStatement(tx.Factory().InlineExpressions(expressions))
|
|
tx.EmitContext().SetOriginal(statement, node.AsNode())
|
|
tx.EmitContext().SetCommentRange(statement, node.Loc)
|
|
tx.EmitContext().SetSourceMapRange(statement, node.Loc)
|
|
return statement
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (tx *usingDeclarationTransformer) hoistInitializedVariable(node *ast.VariableDeclaration) *ast.Expression {
|
|
// NOTE: `node` has already been visited
|
|
if node.Initializer == nil {
|
|
panic("Expected initializer")
|
|
}
|
|
var target *ast.Expression
|
|
if ast.IsIdentifier(node.Name()) {
|
|
target = node.Name().Clone(tx.Factory())
|
|
tx.EmitContext().SetEmitFlags(target, tx.EmitContext().EmitFlags(target) & ^(printer.EFLocalName|printer.EFExportName|printer.EFInternalName))
|
|
} else {
|
|
target = transformers.ConvertBindingPatternToAssignmentPattern(tx.EmitContext(), node.Name().AsBindingPattern())
|
|
}
|
|
|
|
assignment := tx.Factory().NewAssignmentExpression(target, node.Initializer)
|
|
tx.EmitContext().SetOriginal(assignment, node.AsNode())
|
|
tx.EmitContext().SetCommentRange(assignment, node.Loc)
|
|
tx.EmitContext().SetSourceMapRange(assignment, node.Loc)
|
|
return assignment
|
|
}
|
|
|
|
func (tx *usingDeclarationTransformer) hoistBindingElement(node *ast.Node /*VariableDeclaration|BindingElement*/, isExportedDeclaration bool, original *ast.Node) {
|
|
// NOTE: `node` has already been visited
|
|
if ast.IsBindingPattern(node.Name()) {
|
|
for _, element := range node.Name().AsBindingPattern().Elements.Nodes {
|
|
if element.Name() != nil {
|
|
tx.hoistBindingElement(element, isExportedDeclaration, original)
|
|
}
|
|
}
|
|
} else {
|
|
tx.hoistBindingIdentifier(node.Name(), isExportedDeclaration, nil /*exportAlias*/, original)
|
|
}
|
|
}
|
|
|
|
func (tx *usingDeclarationTransformer) hoistBindingIdentifier(node *ast.IdentifierNode, isExport bool, exportAlias *ast.IdentifierNode, original *ast.Node) {
|
|
// NOTE: `node` has already been visited
|
|
name := node
|
|
if !transformers.IsGeneratedIdentifier(tx.EmitContext(), node) {
|
|
name = name.Clone(tx.Factory())
|
|
}
|
|
if isExport {
|
|
if exportAlias == nil && !transformers.IsLocalName(tx.EmitContext(), name) {
|
|
varDecl := tx.Factory().NewVariableDeclaration(name, nil /*exclamationToken*/, nil /*type*/, nil /*initializer*/)
|
|
if original != nil {
|
|
tx.EmitContext().SetOriginal(varDecl, original)
|
|
}
|
|
tx.exportVars = append(tx.exportVars, varDecl)
|
|
return
|
|
}
|
|
|
|
var localName *ast.ModuleExportName
|
|
var exportName *ast.ModuleExportName
|
|
if exportAlias != nil {
|
|
localName = name
|
|
exportName = exportAlias
|
|
} else {
|
|
exportName = name
|
|
}
|
|
specifier := tx.Factory().NewExportSpecifier( /*isTypeOnly*/ false, localName, exportName)
|
|
if original != nil {
|
|
tx.EmitContext().SetOriginal(specifier, original)
|
|
}
|
|
if tx.exportBindings == nil {
|
|
tx.exportBindings = make(map[string]*ast.ExportSpecifierNode)
|
|
}
|
|
tx.exportBindings[name.Text()] = specifier
|
|
}
|
|
tx.EmitContext().AddVariableDeclaration(name)
|
|
}
|
|
|
|
func (tx *usingDeclarationTransformer) createEnvBinding() *ast.IdentifierNode {
|
|
return tx.Factory().NewUniqueName("env")
|
|
}
|
|
|
|
func (tx *usingDeclarationTransformer) createDownlevelUsingStatements(bodyStatements []*ast.Node, envBinding *ast.IdentifierNode, async bool) []*ast.Statement {
|
|
statements := make([]*ast.Statement, 0, 2)
|
|
|
|
// produces:
|
|
//
|
|
// const env_1 = { stack: [], error: void 0, hasError: false };
|
|
//
|
|
envObject := tx.Factory().NewObjectLiteralExpression(tx.Factory().NewNodeList([]*ast.Expression{
|
|
tx.Factory().NewPropertyAssignment(nil /*modifiers*/, tx.Factory().NewIdentifier("stack"), nil /*postfixToken*/, nil /*typeNode*/, tx.Factory().NewArrayLiteralExpression(nil, false /*multiLine*/)),
|
|
tx.Factory().NewPropertyAssignment(nil /*modifiers*/, tx.Factory().NewIdentifier("error"), nil /*postfixToken*/, nil /*typeNode*/, tx.Factory().NewVoidZeroExpression()),
|
|
tx.Factory().NewPropertyAssignment(nil /*modifiers*/, tx.Factory().NewIdentifier("hasError"), nil /*postfixToken*/, nil /*typeNode*/, tx.Factory().NewFalseExpression()),
|
|
}), false /*multiLine*/)
|
|
envVar := tx.Factory().NewVariableDeclaration(envBinding, nil /*exclamationToken*/, nil /*typeNode*/, envObject)
|
|
envVarList := tx.Factory().NewVariableDeclarationList(ast.NodeFlagsConst, tx.Factory().NewNodeList([]*ast.VariableDeclarationNode{envVar}))
|
|
envVarStatement := tx.Factory().NewVariableStatement(nil /*modifiers*/, envVarList)
|
|
statements = append(statements, envVarStatement)
|
|
|
|
// when `async` is `false`, produces:
|
|
//
|
|
// try {
|
|
// <bodyStatements>
|
|
// }
|
|
// catch (e_1) {
|
|
// env_1.error = e_1;
|
|
// env_1.hasError = true;
|
|
// }
|
|
// finally {
|
|
// __disposeResources(env_1);
|
|
// }
|
|
|
|
// when `async` is `true`, produces:
|
|
//
|
|
// try {
|
|
// <bodyStatements>
|
|
// }
|
|
// catch (e_1) {
|
|
// env_1.error = e_1;
|
|
// env_1.hasError = true;
|
|
// }
|
|
// finally {
|
|
// const result_1 = __disposeResources(env_1);
|
|
// if (result_1) {
|
|
// await result_1;
|
|
// }
|
|
// }
|
|
|
|
// Unfortunately, it is necessary to use two properties to indicate an error because `throw undefined` is legal
|
|
// JavaScript.
|
|
tryBlock := tx.Factory().NewBlock(tx.Factory().NewNodeList(bodyStatements), true /*multiLine*/)
|
|
bodyCatchBinding := tx.Factory().NewUniqueName("e")
|
|
catchClause := tx.Factory().NewCatchClause(
|
|
tx.Factory().NewVariableDeclaration(
|
|
bodyCatchBinding,
|
|
nil, /*exclamationToken*/
|
|
nil, /*type*/
|
|
nil, /*initializer*/
|
|
),
|
|
tx.Factory().NewBlock(tx.Factory().NewNodeList([]*ast.Statement{
|
|
tx.Factory().NewExpressionStatement(
|
|
tx.Factory().NewAssignmentExpression(
|
|
tx.Factory().NewPropertyAccessExpression(envBinding, nil, tx.Factory().NewIdentifier("error"), ast.NodeFlagsNone),
|
|
bodyCatchBinding,
|
|
),
|
|
),
|
|
tx.Factory().NewExpressionStatement(
|
|
tx.Factory().NewAssignmentExpression(
|
|
tx.Factory().NewPropertyAccessExpression(envBinding, nil, tx.Factory().NewIdentifier("hasError"), ast.NodeFlagsNone),
|
|
tx.Factory().NewTrueExpression(),
|
|
),
|
|
),
|
|
}), true /*multiLine*/),
|
|
)
|
|
|
|
var finallyBlock *ast.BlockNode
|
|
if async {
|
|
result := tx.Factory().NewUniqueName("result")
|
|
finallyBlock = tx.Factory().NewBlock(tx.Factory().NewNodeList([]*ast.Statement{
|
|
tx.Factory().NewVariableStatement(
|
|
nil, /*modifiers*/
|
|
tx.Factory().NewVariableDeclarationList(ast.NodeFlagsConst, tx.Factory().NewNodeList([]*ast.VariableDeclarationNode{
|
|
tx.Factory().NewVariableDeclaration(
|
|
result,
|
|
nil, /*exclamationToken*/
|
|
nil, /*type*/
|
|
tx.Factory().NewDisposeResourcesHelper(envBinding),
|
|
),
|
|
})),
|
|
),
|
|
tx.Factory().NewIfStatement(result, tx.Factory().NewExpressionStatement(tx.Factory().NewAwaitExpression(result)), nil /*elseStatement*/),
|
|
}), true /*multiLine*/)
|
|
} else {
|
|
finallyBlock = tx.Factory().NewBlock(tx.Factory().NewNodeList([]*ast.Statement{
|
|
tx.Factory().NewExpressionStatement(
|
|
tx.Factory().NewDisposeResourcesHelper(envBinding),
|
|
),
|
|
}), true /*multiLine*/)
|
|
}
|
|
|
|
tryStatement := tx.Factory().NewTryStatement(tryBlock, catchClause, finallyBlock)
|
|
statements = append(statements, tryStatement)
|
|
return statements
|
|
}
|
|
|
|
func isUsingVariableDeclarationList(node *ast.ForInitializer) bool {
|
|
return ast.IsVariableDeclarationList(node) && getUsingKindOfVariableDeclarationList(node.AsVariableDeclarationList()) != usingKindNone
|
|
}
|
|
|
|
func getUsingKindOfVariableDeclarationList(node *ast.VariableDeclarationList) usingKind {
|
|
switch node.Flags & ast.NodeFlagsBlockScoped {
|
|
case ast.NodeFlagsAwaitUsing:
|
|
return usingKindAsync
|
|
case ast.NodeFlagsUsing:
|
|
return usingKindSync
|
|
default:
|
|
return usingKindNone
|
|
}
|
|
}
|
|
|
|
func getUsingKindOfVariableStatement(node *ast.VariableStatement) usingKind {
|
|
return getUsingKindOfVariableDeclarationList(node.DeclarationList.AsVariableDeclarationList())
|
|
}
|
|
|
|
func getUsingKind(statement *ast.Node) usingKind {
|
|
if ast.IsVariableStatement(statement) {
|
|
return getUsingKindOfVariableStatement(statement.AsVariableStatement())
|
|
}
|
|
return usingKindNone
|
|
}
|
|
|
|
func getUsingKindOfStatements(statements []*ast.Node) usingKind {
|
|
result := usingKindNone
|
|
for _, statement := range statements {
|
|
usingKind := getUsingKind(statement)
|
|
if usingKind == usingKindAsync {
|
|
return usingKindAsync
|
|
}
|
|
if usingKind > result {
|
|
result = usingKind
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func getUsingKindOfCaseOrDefaultClauses(clauses []*ast.CaseOrDefaultClauseNode) usingKind {
|
|
result := usingKindNone
|
|
for _, clause := range clauses {
|
|
usingKind := getUsingKindOfStatements(clause.AsCaseOrDefaultClause().Statements.Nodes)
|
|
if usingKind == usingKindAsync {
|
|
return usingKindAsync
|
|
}
|
|
if usingKind > result {
|
|
result = usingKind
|
|
}
|
|
}
|
|
return result
|
|
}
|