2025-10-15 10:12:44 +03:00

275 lines
10 KiB
Go

package transformers
import (
"slices"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/printer"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/scanner"
)
func IsGeneratedIdentifier(emitContext *printer.EmitContext, name *ast.IdentifierNode) bool {
return emitContext.HasAutoGenerateInfo(name)
}
func IsHelperName(emitContext *printer.EmitContext, name *ast.IdentifierNode) bool {
return emitContext.EmitFlags(name)&printer.EFHelperName != 0
}
func IsLocalName(emitContext *printer.EmitContext, name *ast.IdentifierNode) bool {
return emitContext.EmitFlags(name)&printer.EFLocalName != 0
}
func IsExportName(emitContext *printer.EmitContext, name *ast.IdentifierNode) bool {
return emitContext.EmitFlags(name)&printer.EFExportName != 0
}
func IsIdentifierReference(name *ast.IdentifierNode, parent *ast.Node) bool {
switch parent.Kind {
case ast.KindBinaryExpression,
ast.KindPrefixUnaryExpression,
ast.KindPostfixUnaryExpression,
ast.KindYieldExpression,
ast.KindAsExpression,
ast.KindSatisfiesExpression,
ast.KindElementAccessExpression,
ast.KindNonNullExpression,
ast.KindSpreadElement,
ast.KindSpreadAssignment,
ast.KindParenthesizedExpression,
ast.KindArrayLiteralExpression,
ast.KindDeleteExpression,
ast.KindTypeOfExpression,
ast.KindVoidExpression,
ast.KindAwaitExpression,
ast.KindTypeAssertionExpression,
ast.KindExpressionWithTypeArguments,
ast.KindJsxSelfClosingElement,
ast.KindJsxSpreadAttribute,
ast.KindJsxExpression,
ast.KindCommaListExpression,
ast.KindPartiallyEmittedExpression:
// all immediate children that can be `Identifier` would be instances of `IdentifierReference`
return true
case ast.KindComputedPropertyName,
ast.KindDecorator,
ast.KindIfStatement,
ast.KindDoStatement,
ast.KindWhileStatement,
ast.KindWithStatement,
ast.KindReturnStatement,
ast.KindSwitchStatement,
ast.KindCaseClause,
ast.KindThrowStatement,
ast.KindExpressionStatement,
ast.KindExportAssignment,
ast.KindJSExportAssignment,
ast.KindPropertyAccessExpression,
ast.KindTemplateSpan:
// only an `Expression()` child that can be `Identifier` would be an instance of `IdentifierReference`
return parent.Expression() == name
case ast.KindVariableDeclaration,
ast.KindParameter,
ast.KindBindingElement,
ast.KindPropertyDeclaration,
ast.KindPropertySignature,
ast.KindPropertyAssignment,
ast.KindEnumMember,
ast.KindJsxAttribute:
// only an `Initializer()` child that can be `Identifier` would be an instance of `IdentifierReference`
return parent.Initializer() == name
case ast.KindForStatement:
return parent.AsForStatement().Initializer == name ||
parent.AsForStatement().Condition == name ||
parent.AsForStatement().Incrementor == name
case ast.KindForInStatement,
ast.KindForOfStatement:
return parent.AsForInOrOfStatement().Initializer == name ||
parent.AsForInOrOfStatement().Expression == name
case ast.KindImportEqualsDeclaration:
return parent.AsImportEqualsDeclaration().ModuleReference == name
case ast.KindArrowFunction:
return parent.AsArrowFunction().Body == name
case ast.KindConditionalExpression:
return parent.AsConditionalExpression().Condition == name ||
parent.AsConditionalExpression().WhenTrue == name ||
parent.AsConditionalExpression().WhenFalse == name
case ast.KindCallExpression:
return parent.AsCallExpression().Expression == name ||
slices.Contains(parent.AsCallExpression().Arguments.Nodes, name)
case ast.KindNewExpression:
return parent.AsNewExpression().Expression == name ||
parent.AsNewExpression().Arguments.Nodes != nil &&
slices.Contains(parent.AsNewExpression().Arguments.Nodes, name)
case ast.KindTaggedTemplateExpression:
return parent.AsTaggedTemplateExpression().Tag == name
case ast.KindImportAttribute:
return parent.AsImportAttribute().Value == name
case ast.KindJsxOpeningElement:
return parent.AsJsxOpeningElement().TagName == name
case ast.KindJsxClosingElement:
return parent.AsJsxClosingElement().TagName == name
default:
return false
}
}
func convertBindingElementToArrayAssignmentElement(emitContext *printer.EmitContext, element *ast.BindingElement) *ast.Expression {
if element.Name() == nil {
elision := emitContext.Factory.NewOmittedExpression()
emitContext.SetOriginal(elision, element.AsNode())
emitContext.AssignCommentAndSourceMapRanges(elision, element.AsNode())
return elision
}
if element.DotDotDotToken != nil {
spread := emitContext.Factory.NewSpreadElement(element.Name())
emitContext.SetOriginal(spread, element.AsNode())
emitContext.AssignCommentAndSourceMapRanges(spread, element.AsNode())
return spread
}
expression := convertBindingNameToAssignmentElementTarget(emitContext, element.Name())
if element.Initializer != nil {
assignment := emitContext.Factory.NewAssignmentExpression(expression, element.Initializer)
emitContext.SetOriginal(assignment, element.AsNode())
emitContext.AssignCommentAndSourceMapRanges(assignment, element.AsNode())
return assignment
}
return expression
}
func convertBindingElementToObjectAssignmentElement(emitContext *printer.EmitContext, element *ast.BindingElement) *ast.ObjectLiteralElement {
if element.DotDotDotToken != nil {
spread := emitContext.Factory.NewSpreadAssignment(element.Name())
emitContext.SetOriginal(spread, element.AsNode())
emitContext.AssignCommentAndSourceMapRanges(spread, element.AsNode())
return spread
}
if element.PropertyName != nil {
expression := convertBindingNameToAssignmentElementTarget(emitContext, element.Name())
if element.Initializer != nil {
expression = emitContext.Factory.NewAssignmentExpression(expression, element.Initializer)
}
assignment := emitContext.Factory.NewPropertyAssignment(nil /*modifiers*/, element.PropertyName, nil /*postfixToken*/, nil /*typeNode*/, expression)
emitContext.SetOriginal(assignment, element.AsNode())
emitContext.AssignCommentAndSourceMapRanges(assignment, element.AsNode())
return assignment
}
var equalsToken *ast.TokenNode
if element.Initializer != nil {
equalsToken = emitContext.Factory.NewToken(ast.KindEqualsToken)
}
assignment := emitContext.Factory.NewShorthandPropertyAssignment(
nil, /*modifiers*/
element.Name(),
nil, /*postfixToken*/
nil, /*typeNode*/
equalsToken,
element.Initializer,
)
emitContext.SetOriginal(assignment, element.AsNode())
emitContext.AssignCommentAndSourceMapRanges(assignment, element.AsNode())
return assignment
}
func ConvertBindingPatternToAssignmentPattern(emitContext *printer.EmitContext, element *ast.BindingPattern) *ast.Expression {
switch element.Kind {
case ast.KindArrayBindingPattern:
return convertBindingElementToArrayAssignmentPattern(emitContext, element)
case ast.KindObjectBindingPattern:
return convertBindingElementToObjectAssignmentPattern(emitContext, element)
default:
panic("Unknown binding pattern")
}
}
func convertBindingElementToObjectAssignmentPattern(emitContext *printer.EmitContext, element *ast.BindingPattern) *ast.Expression {
var properties []*ast.ObjectLiteralElement
for _, element := range element.Elements.Nodes {
properties = append(properties, convertBindingElementToObjectAssignmentElement(emitContext, element.AsBindingElement()))
}
propertyList := emitContext.Factory.NewNodeList(properties)
propertyList.Loc = element.Elements.Loc
object := emitContext.Factory.NewObjectLiteralExpression(propertyList, false /*multiLine*/)
emitContext.SetOriginal(object, element.AsNode())
emitContext.AssignCommentAndSourceMapRanges(object, element.AsNode())
return object
}
func convertBindingElementToArrayAssignmentPattern(emitContext *printer.EmitContext, element *ast.BindingPattern) *ast.Expression {
var elements []*ast.Expression
for _, element := range element.Elements.Nodes {
elements = append(elements, convertBindingElementToArrayAssignmentElement(emitContext, element.AsBindingElement()))
}
elementList := emitContext.Factory.NewNodeList(elements)
elementList.Loc = element.Elements.Loc
object := emitContext.Factory.NewArrayLiteralExpression(elementList, false /*multiLine*/)
emitContext.SetOriginal(object, element.AsNode())
emitContext.AssignCommentAndSourceMapRanges(object, element.AsNode())
return object
}
func convertBindingNameToAssignmentElementTarget(emitContext *printer.EmitContext, element *ast.Node) *ast.Expression {
if ast.IsBindingPattern(element) {
return ConvertBindingPatternToAssignmentPattern(emitContext, element.AsBindingPattern())
}
return element
}
func ConvertVariableDeclarationToAssignmentExpression(emitContext *printer.EmitContext, element *ast.VariableDeclaration) *ast.Expression {
if element.Initializer == nil {
return nil
}
expression := convertBindingNameToAssignmentElementTarget(emitContext, element.Name())
assignment := emitContext.Factory.NewAssignmentExpression(expression, element.Initializer)
emitContext.SetOriginal(assignment, element.AsNode())
emitContext.AssignCommentAndSourceMapRanges(assignment, element.AsNode())
return assignment
}
func SingleOrMany(nodes []*ast.Node, factory *printer.NodeFactory) *ast.Node {
if len(nodes) == 1 {
return nodes[0]
}
return factory.NewSyntaxList(nodes)
}
// Used in the module transformer to check if an expression is reasonably without sideeffect,
//
// and thus better to copy into multiple places rather than to cache in a temporary variable
// - this is mostly subjective beyond the requirement that the expression not be sideeffecting
//
// Also used by the logical assignment downleveling transform to skip temp variables when they're
// not needed.
func IsSimpleCopiableExpression(expression *ast.Expression) bool {
return ast.IsStringLiteralLike(expression) ||
ast.IsNumericLiteral(expression) ||
ast.IsKeywordKind(expression.Kind) ||
ast.IsIdentifier(expression)
}
func IsOriginalNodeSingleLine(emitContext *printer.EmitContext, node *ast.Node) bool {
if node == nil {
return false
}
original := emitContext.MostOriginal(node)
if original == nil {
return false
}
source := ast.GetSourceFileOfNode(original)
if source == nil {
return false
}
startLine, _ := scanner.GetECMALineAndCharacterOfPosition(source, original.Loc.Pos())
endLine, _ := scanner.GetECMALineAndCharacterOfPosition(source, original.Loc.End())
return startLine == endLine
}
/**
* A simple inlinable expression is an expression which can be copied into multiple locations
* without risk of repeating any sideeffects and whose value could not possibly change between
* any such locations
*/
func IsSimpleInlineableExpression(expression *ast.Expression) bool {
return !ast.IsIdentifier(expression) && IsSimpleCopiableExpression(expression)
}