package ast

import (
	"fmt"
	"slices"
	"strings"
	"sync"
	"sync/atomic"

	"github.com/microsoft/typescript-go/internal/core"
	"github.com/microsoft/typescript-go/internal/debug"
	"github.com/microsoft/typescript-go/internal/tspath"
)

// Atomic ids

var (
	nextNodeId   atomic.Uint64
	nextSymbolId atomic.Uint64
)

func GetNodeId(node *Node) NodeId {
	id := node.id.Load()
	if id == 0 {
		// Worst case, we burn a few ids if we have to CAS.
		id = nextNodeId.Add(1)
		if !node.id.CompareAndSwap(0, id) {
			id = node.id.Load()
		}
	}
	return NodeId(id)
}

func GetSymbolId(symbol *Symbol) SymbolId {
	id := symbol.id.Load()
	if id == 0 {
		// Worst case, we burn a few ids if we have to CAS.
		id = nextSymbolId.Add(1)
		if !symbol.id.CompareAndSwap(0, id) {
			id = symbol.id.Load()
		}
	}
	return SymbolId(id)
}

func GetSymbolTable(data *SymbolTable) SymbolTable {
	if *data == nil {
		*data = make(SymbolTable)
	}
	return *data
}

func GetMembers(symbol *Symbol) SymbolTable {
	return GetSymbolTable(&symbol.Members)
}

func GetExports(symbol *Symbol) SymbolTable {
	return GetSymbolTable(&symbol.Exports)
}

func GetLocals(container *Node) SymbolTable {
	return GetSymbolTable(&container.LocalsContainerData().Locals)
}

// Determines if a node is missing (either `nil` or empty)
func NodeIsMissing(node *Node) bool {
	return node == nil || node.Loc.Pos() == node.Loc.End() && node.Loc.Pos() >= 0 && node.Kind != KindEndOfFile
}

// Determines if a node is present
func NodeIsPresent(node *Node) bool {
	return !NodeIsMissing(node)
}

// Determines if a node contains synthetic positions
func NodeIsSynthesized(node *Node) bool {
	return PositionIsSynthesized(node.Loc.Pos()) || PositionIsSynthesized(node.Loc.End())
}

func RangeIsSynthesized(loc core.TextRange) bool {
	return PositionIsSynthesized(loc.Pos()) || PositionIsSynthesized(loc.End())
}

// Determines whether a position is synthetic
func PositionIsSynthesized(pos int) bool {
	return pos < 0
}

func FindLastVisibleNode(nodes []*Node) *Node {
	fromEnd := 1
	for fromEnd <= len(nodes) && nodes[len(nodes)-fromEnd].Flags&NodeFlagsReparsed != 0 {
		fromEnd++
	}
	if fromEnd <= len(nodes) {
		return nodes[len(nodes)-fromEnd]
	}
	return nil
}

func NodeKindIs(node *Node, kinds ...Kind) bool {
	return slices.Contains(kinds, node.Kind)
}

func IsModifier(node *Node) bool {
	return IsModifierKind(node.Kind)
}

func IsModifierLike(node *Node) bool {
	return IsModifier(node) || IsDecorator(node)
}

func IsCompoundAssignment(token Kind) bool {
	return token >= KindFirstCompoundAssignment && token <= KindLastCompoundAssignment
}

func IsAssignmentExpression(node *Node, excludeCompoundAssignment bool) bool {
	if node.Kind == KindBinaryExpression {
		expr := node.AsBinaryExpression()
		return (expr.OperatorToken.Kind == KindEqualsToken || !excludeCompoundAssignment && IsAssignmentOperator(expr.OperatorToken.Kind)) &&
			IsLeftHandSideExpression(expr.Left)
	}
	return false
}

func GetRightMostAssignedExpression(node *Node) *Node {
	for IsAssignmentExpression(node, false /*excludeCompoundAssignment*/) {
		node = node.AsBinaryExpression().Right
	}
	return node
}

func IsDestructuringAssignment(node *Node) bool {
	if IsAssignmentExpression(node, true /*excludeCompoundAssignment*/) {
		kind := node.AsBinaryExpression().Left.Kind
		return kind == KindObjectLiteralExpression || kind == KindArrayLiteralExpression
	}
	return false
}

func IsObjectBindingOrAssignmentElement(node *Node) bool {
	switch node.Kind {
	case KindBindingElement,
		KindPropertyAssignment,
		KindShorthandPropertyAssignment,
		KindSpreadAssignment:
		return true
	}
	return false
}

func IsArrayBindingOrAssignmentElement(node *Node) bool {
	switch node.Kind {
	case KindBindingElement,
		KindOmittedExpression,
		KindSpreadElement,
		KindArrayLiteralExpression,
		KindObjectLiteralExpression,
		KindIdentifier,
		KindPropertyAccessExpression,
		KindElementAccessExpression:
		return true
	}
	return IsAssignmentExpression(node, true /*excludeCompoundAssignment*/)
}

func IsBindingPattern(node *Node) bool {
	return node.Kind == KindObjectBindingPattern || node.Kind == KindArrayBindingPattern
}

func IsForInOrOfStatement(node *Node) bool {
	return node != nil && (node.Kind == KindForInStatement || node.Kind == KindForOfStatement)
}

// A node is an assignment target if it is on the left hand side of an '=' token, if it is parented by a property
// assignment in an object literal that is an assignment target, or if it is parented by an array literal that is
// an assignment target. Examples include 'a = xxx', '{ p: a } = xxx', '[{ a }] = xxx'.
// (Note that `p` is not a target in the above examples, only `a`.)
func IsAssignmentTarget(node *Node) bool {
	return GetAssignmentTarget(node) != nil
}

// Returns the BinaryExpression, PrefixUnaryExpression, PostfixUnaryExpression, or ForInOrOfStatement that references
// the given node as an assignment target
func GetAssignmentTarget(node *Node) *Node {
	for {
		parent := node.Parent
		switch parent.Kind {
		case KindBinaryExpression:
			if IsAssignmentOperator(parent.AsBinaryExpression().OperatorToken.Kind) && parent.AsBinaryExpression().Left == node {
				return parent
			}
			return nil
		case KindPrefixUnaryExpression:
			if parent.AsPrefixUnaryExpression().Operator == KindPlusPlusToken || parent.AsPrefixUnaryExpression().Operator == KindMinusMinusToken {
				return parent
			}
			return nil
		case KindPostfixUnaryExpression:
			if parent.AsPostfixUnaryExpression().Operator == KindPlusPlusToken || parent.AsPostfixUnaryExpression().Operator == KindMinusMinusToken {
				return parent
			}
			return nil
		case KindForInStatement, KindForOfStatement:
			if parent.Initializer() == node {
				return parent
			}
			return nil
		case KindParenthesizedExpression, KindArrayLiteralExpression, KindSpreadElement, KindNonNullExpression:
			node = parent
		case KindSpreadAssignment:
			node = parent.Parent
		case KindShorthandPropertyAssignment:
			if parent.AsShorthandPropertyAssignment().Name() != node {
				return nil
			}
			node = parent.Parent
		case KindPropertyAssignment:
			if parent.AsPropertyAssignment().Name() == node {
				return nil
			}
			node = parent.Parent
		default:
			return nil
		}
	}
}

func IsLogicalBinaryOperator(token Kind) bool {
	return token == KindBarBarToken || token == KindAmpersandAmpersandToken
}

func IsLogicalOrCoalescingBinaryOperator(token Kind) bool {
	return IsLogicalBinaryOperator(token) || token == KindQuestionQuestionToken
}

func IsLogicalOrCoalescingBinaryExpression(expr *Node) bool {
	return IsBinaryExpression(expr) && IsLogicalOrCoalescingBinaryOperator(expr.AsBinaryExpression().OperatorToken.Kind)
}

func IsLogicalOrCoalescingAssignmentExpression(expr *Node) bool {
	return IsBinaryExpression(expr) && IsLogicalOrCoalescingAssignmentOperator(expr.AsBinaryExpression().OperatorToken.Kind)
}

func IsLogicalExpression(node *Node) bool {
	for {
		if node.Kind == KindParenthesizedExpression {
			node = node.Expression()
		} else if node.Kind == KindPrefixUnaryExpression && node.AsPrefixUnaryExpression().Operator == KindExclamationToken {
			node = node.AsPrefixUnaryExpression().Operand
		} else {
			return IsLogicalOrCoalescingBinaryExpression(node)
		}
	}
}

func IsAccessor(node *Node) bool {
	return node.Kind == KindGetAccessor || node.Kind == KindSetAccessor
}

func IsPropertyNameLiteral(node *Node) bool {
	switch node.Kind {
	case KindIdentifier,
		KindStringLiteral,
		KindNoSubstitutionTemplateLiteral,
		KindNumericLiteral:
		return true
	}
	return false
}

func IsMemberName(node *Node) bool {
	return node.Kind == KindIdentifier || node.Kind == KindPrivateIdentifier
}

func IsEntityName(node *Node) bool {
	return node.Kind == KindIdentifier || node.Kind == KindQualifiedName
}

func IsPropertyName(node *Node) bool {
	switch node.Kind {
	case KindIdentifier,
		KindPrivateIdentifier,
		KindStringLiteral,
		KindNumericLiteral,
		KindComputedPropertyName:
		return true
	}
	return false
}

// Return true if the given identifier is classified as an IdentifierName by inspecting the parent of the node
func IsIdentifierName(node *Node) bool {
	parent := node.Parent
	switch parent.Kind {
	case KindPropertyDeclaration, KindPropertySignature, KindMethodDeclaration, KindMethodSignature, KindGetAccessor,
		KindSetAccessor, KindEnumMember, KindPropertyAssignment, KindPropertyAccessExpression:
		return parent.Name() == node
	case KindQualifiedName:
		return parent.AsQualifiedName().Right == node
	case KindBindingElement:
		return parent.PropertyName() == node
	case KindImportSpecifier:
		return parent.PropertyName() == node
	case KindExportSpecifier, KindJsxAttribute, KindJsxSelfClosingElement, KindJsxOpeningElement, KindJsxClosingElement:
		return true
	}
	return false
}

func IsPushOrUnshiftIdentifier(node *Node) bool {
	text := node.Text()
	return text == "push" || text == "unshift"
}

func IsBooleanLiteral(node *Node) bool {
	return node.Kind == KindTrueKeyword || node.Kind == KindFalseKeyword
}

func IsLiteralExpression(node *Node) bool {
	return IsLiteralKind(node.Kind)
}

func IsStringLiteralLike(node *Node) bool {
	switch node.Kind {
	case KindStringLiteral, KindNoSubstitutionTemplateLiteral:
		return true
	}
	return false
}

func IsStringOrNumericLiteralLike(node *Node) bool {
	return IsStringLiteralLike(node) || IsNumericLiteral(node)
}

func IsSignedNumericLiteral(node *Node) bool {
	if node.Kind == KindPrefixUnaryExpression {
		node := node.AsPrefixUnaryExpression()
		return (node.Operator == KindPlusToken || node.Operator == KindMinusToken) && IsNumericLiteral(node.Operand)
	}
	return false
}

// Determines if a node is part of an OptionalChain
func IsOptionalChain(node *Node) bool {
	if node.Flags&NodeFlagsOptionalChain != 0 {
		switch node.Kind {
		case KindPropertyAccessExpression,
			KindElementAccessExpression,
			KindCallExpression,
			KindNonNullExpression:
			return true
		}
	}
	return false
}

func getQuestionDotToken(node *Expression) *TokenNode {
	return node.QuestionDotToken()
}

// Determines if node is the root expression of an OptionalChain
func IsOptionalChainRoot(node *Expression) bool {
	return IsOptionalChain(node) && !IsNonNullExpression(node) && getQuestionDotToken(node) != nil
}

// Determines whether a node is the outermost `OptionalChain` in an ECMAScript `OptionalExpression`:
//
//  1. For `a?.b.c`, the outermost chain is `a?.b.c` (`c` is the end of the chain starting at `a?.`)
//  2. For `a?.b!`, the outermost chain is `a?.b` (`b` is the end of the chain starting at `a?.`)
//  3. For `(a?.b.c).d`, the outermost chain is `a?.b.c` (`c` is the end of the chain starting at `a?.` since parens end the chain)
//  4. For `a?.b.c?.d`, both `a?.b.c` and `a?.b.c?.d` are outermost (`c` is the end of the chain starting at `a?.`, and `d` is
//     the end of the chain starting at `c?.`)
//  5. For `a?.(b?.c).d`, both `b?.c` and `a?.(b?.c)d` are outermost (`c` is the end of the chain starting at `b`, and `d` is
//     the end of the chain starting at `a?.`)
func IsOutermostOptionalChain(node *Expression) bool {
	parent := node.Parent
	return !IsOptionalChain(parent) || // cases 1, 2, and 3
		IsOptionalChainRoot(parent) || // case 4
		node != parent.Expression() // case 5
}

// Determines whether a node is the expression preceding an optional chain (i.e. `a` in `a?.b`).
func IsExpressionOfOptionalChainRoot(node *Node) bool {
	return IsOptionalChainRoot(node.Parent) && node.Parent.Expression() == node
}

func IsNullishCoalesce(node *Node) bool {
	return node.Kind == KindBinaryExpression && node.AsBinaryExpression().OperatorToken.Kind == KindQuestionQuestionToken
}

func IsAssertionExpression(node *Node) bool {
	kind := node.Kind
	return kind == KindTypeAssertionExpression || kind == KindAsExpression
}

func isLeftHandSideExpressionKind(kind Kind) bool {
	switch kind {
	case KindPropertyAccessExpression, KindElementAccessExpression, KindNewExpression, KindCallExpression,
		KindJsxElement, KindJsxSelfClosingElement, KindJsxFragment, KindTaggedTemplateExpression, KindArrayLiteralExpression,
		KindParenthesizedExpression, KindObjectLiteralExpression, KindClassExpression, KindFunctionExpression, KindIdentifier,
		KindPrivateIdentifier, KindRegularExpressionLiteral, KindNumericLiteral, KindBigIntLiteral, KindStringLiteral,
		KindNoSubstitutionTemplateLiteral, KindTemplateExpression, KindFalseKeyword, KindNullKeyword, KindThisKeyword,
		KindTrueKeyword, KindSuperKeyword, KindNonNullExpression, KindExpressionWithTypeArguments, KindMetaProperty,
		KindImportKeyword, KindMissingDeclaration:
		return true
	}
	return false
}

// Determines whether a node is a LeftHandSideExpression based only on its kind.
func IsLeftHandSideExpression(node *Node) bool {
	return isLeftHandSideExpressionKind(SkipPartiallyEmittedExpressions(node).Kind)
}

func isUnaryExpressionKind(kind Kind) bool {
	switch kind {
	case KindPrefixUnaryExpression,
		KindPostfixUnaryExpression,
		KindDeleteExpression,
		KindTypeOfExpression,
		KindVoidExpression,
		KindAwaitExpression,
		KindTypeAssertionExpression:
		return true
	}
	return isLeftHandSideExpressionKind(kind)
}

// Determines whether a node is a UnaryExpression based only on its kind.
func IsUnaryExpression(node *Node) bool {
	return isUnaryExpressionKind(SkipPartiallyEmittedExpressions(node).Kind)
}

func isExpressionKind(kind Kind) bool {
	switch kind {
	case KindConditionalExpression,
		KindYieldExpression,
		KindArrowFunction,
		KindBinaryExpression,
		KindSpreadElement,
		KindAsExpression,
		KindOmittedExpression,
		KindPartiallyEmittedExpression,
		KindSatisfiesExpression:
		return true
	}
	return isUnaryExpressionKind(kind)
}

// Determines whether a node is an expression based only on its kind.
func IsExpression(node *Node) bool {
	return isExpressionKind(SkipPartiallyEmittedExpressions(node).Kind)
}

func IsCommaExpression(node *Node) bool {
	return node.Kind == KindBinaryExpression && node.AsBinaryExpression().OperatorToken.Kind == KindCommaToken
}

func IsCommaSequence(node *Node) bool {
	return IsCommaExpression(node)
}

func IsIterationStatement(node *Node, lookInLabeledStatements bool) bool {
	switch node.Kind {
	case KindForStatement,
		KindForInStatement,
		KindForOfStatement,
		KindDoStatement,
		KindWhileStatement:
		return true
	case KindLabeledStatement:
		return lookInLabeledStatements && IsIterationStatement(node.Statement(), lookInLabeledStatements)
	}

	return false
}

// Determines if a node is a property or element access expression
func IsAccessExpression(node *Node) bool {
	return node.Kind == KindPropertyAccessExpression || node.Kind == KindElementAccessExpression
}

func isFunctionLikeDeclarationKind(kind Kind) bool {
	switch kind {
	case KindFunctionDeclaration,
		KindMethodDeclaration,
		KindConstructor,
		KindGetAccessor,
		KindSetAccessor,
		KindFunctionExpression,
		KindArrowFunction:
		return true
	}
	return false
}

// Determines if a node is function-like (but is not a signature declaration)
func IsFunctionLikeDeclaration(node *Node) bool {
	// TODO(rbuckton): Move `node != nil` test to call sites
	return node != nil && isFunctionLikeDeclarationKind(node.Kind)
}

func IsFunctionLikeKind(kind Kind) bool {
	switch kind {
	case KindMethodSignature,
		KindCallSignature,
		KindJSDocSignature,
		KindConstructSignature,
		KindIndexSignature,
		KindFunctionType,
		KindConstructorType:
		return true
	}
	return isFunctionLikeDeclarationKind(kind)
}

// Determines if a node is function- or signature-like.
func IsFunctionLike(node *Node) bool {
	// TODO(rbuckton): Move `node != nil` test to call sites
	return node != nil && IsFunctionLikeKind(node.Kind)
}

func IsFunctionLikeOrClassStaticBlockDeclaration(node *Node) bool {
	return node != nil && (IsFunctionLike(node) || IsClassStaticBlockDeclaration(node))
}

func IsFunctionOrSourceFile(node *Node) bool {
	return IsFunctionLike(node) || IsSourceFile(node)
}

func IsClassLike(node *Node) bool {
	return node.Kind == KindClassDeclaration || node.Kind == KindClassExpression
}

func IsClassOrInterfaceLike(node *Node) bool {
	return node.Kind == KindClassDeclaration || node.Kind == KindClassExpression || node.Kind == KindInterfaceDeclaration
}

func IsClassElement(node *Node) bool {
	switch node.Kind {
	case KindConstructor,
		KindPropertyDeclaration,
		KindMethodDeclaration,
		KindGetAccessor,
		KindSetAccessor,
		KindIndexSignature,
		KindClassStaticBlockDeclaration,
		KindSemicolonClassElement:
		return true
	}
	return false
}

func IsMethodOrAccessor(node *Node) bool {
	switch node.Kind {
	case KindMethodDeclaration, KindGetAccessor, KindSetAccessor:
		return true
	}
	return false
}

func IsPrivateIdentifierClassElementDeclaration(node *Node) bool {
	return (IsPropertyDeclaration(node) || IsMethodOrAccessor(node)) && IsPrivateIdentifier(node.Name())
}

func IsObjectLiteralOrClassExpressionMethodOrAccessor(node *Node) bool {
	kind := node.Kind
	return (kind == KindMethodDeclaration || kind == KindGetAccessor || kind == KindSetAccessor) &&
		(node.Parent.Kind == KindObjectLiteralExpression || node.Parent.Kind == KindClassExpression)
}

func IsTypeElement(node *Node) bool {
	switch node.Kind {
	case KindConstructSignature,
		KindCallSignature,
		KindPropertySignature,
		KindMethodSignature,
		KindIndexSignature,
		KindGetAccessor,
		KindSetAccessor,
		KindNotEmittedTypeElement:
		return true
	}
	return false
}

func IsObjectLiteralElement(node *Node) bool {
	switch node.Kind {
	case KindPropertyAssignment,
		KindShorthandPropertyAssignment,
		KindSpreadAssignment,
		KindMethodDeclaration,
		KindGetAccessor,
		KindSetAccessor:
		return true
	}
	return false
}

func IsObjectLiteralMethod(node *Node) bool {
	return node != nil && node.Kind == KindMethodDeclaration && node.Parent.Kind == KindObjectLiteralExpression
}

func IsAutoAccessorPropertyDeclaration(node *Node) bool {
	return IsPropertyDeclaration(node) && HasAccessorModifier(node)
}

func IsParameterPropertyDeclaration(node *Node, parent *Node) bool {
	return IsParameterDeclaration(node) && HasSyntacticModifier(node, ModifierFlagsParameterPropertyModifier) && parent.Kind == KindConstructor
}

func IsJsxChild(node *Node) bool {
	switch node.Kind {
	case KindJsxElement,
		KindJsxExpression,
		KindJsxSelfClosingElement,
		KindJsxText,
		KindJsxFragment:
		return true
	}
	return false
}

func IsJsxAttributeLike(node *Node) bool {
	return IsJsxAttribute(node) || IsJsxSpreadAttribute(node)
}

func isDeclarationStatementKind(kind Kind) bool {
	switch kind {
	case KindFunctionDeclaration,
		KindMissingDeclaration,
		KindClassDeclaration,
		KindInterfaceDeclaration,
		KindTypeAliasDeclaration,
		KindJSTypeAliasDeclaration,
		KindEnumDeclaration,
		KindModuleDeclaration,
		KindImportDeclaration,
		KindJSImportDeclaration,
		KindImportEqualsDeclaration,
		KindExportDeclaration,
		KindExportAssignment,
		KindNamespaceExportDeclaration:
		return true
	}
	return false
}

// Determines whether a node is a DeclarationStatement. Ideally this does not use Parent pointers, but it may use them
// to rule out a Block node that is part of `try` or `catch` or is the Block-like body of a function.
//
// NOTE: ECMA262 would just call this a Declaration
func IsDeclarationStatement(node *Node) bool {
	return isDeclarationStatementKind(node.Kind)
}

func isStatementKindButNotDeclarationKind(kind Kind) bool {
	switch kind {
	case KindBreakStatement,
		KindContinueStatement,
		KindDebuggerStatement,
		KindDoStatement,
		KindExpressionStatement,
		KindEmptyStatement,
		KindForInStatement,
		KindForOfStatement,
		KindForStatement,
		KindIfStatement,
		KindLabeledStatement,
		KindReturnStatement,
		KindSwitchStatement,
		KindThrowStatement,
		KindTryStatement,
		KindVariableStatement,
		KindWhileStatement,
		KindWithStatement,
		KindNotEmittedStatement:
		return true
	}
	return false
}

// Determines whether a node is a Statement that is not also a Declaration. Ideally this does not use Parent pointers,
// but it may use them to rule out a Block node that is part of `try` or `catch` or is the Block-like body of a function.
//
// NOTE: ECMA262 would just call this a Statement
func IsStatementButNotDeclaration(node *Node) bool {
	return isStatementKindButNotDeclarationKind(node.Kind)
}

// Determines whether a node is a Statement. Ideally this does not use Parent pointers, but it may use
// them to rule out a Block node that is part of `try` or `catch` or is the Block-like body of a function.
//
// NOTE: ECMA262 would call this either a StatementListItem or ModuleListItem
func IsStatement(node *Node) bool {
	kind := node.Kind
	return isStatementKindButNotDeclarationKind(kind) || isDeclarationStatementKind(kind) || isBlockStatement(node)
}

// Determines whether a node is a BlockStatement. If parents are available, this ensures the Block is
// not part of a `try` statement, `catch` clause, or the Block-like body of a function
func isBlockStatement(node *Node) bool {
	if node.Kind != KindBlock {
		return false
	}
	if node.Parent != nil && (node.Parent.Kind == KindTryStatement || node.Parent.Kind == KindCatchClause) {
		return false
	}
	return !IsFunctionBlock(node)
}

// Determines whether a node is the Block-like body of a function by walking the parent of the node
func IsFunctionBlock(node *Node) bool {
	return node != nil && node.Kind == KindBlock && node.Parent != nil && IsFunctionLike(node.Parent)
}

func IsBlockOrCatchScoped(declaration *Node) bool {
	return GetCombinedNodeFlags(declaration)&NodeFlagsBlockScoped != 0 || IsCatchClauseVariableDeclarationOrBindingElement(declaration)
}

func IsCatchClauseVariableDeclarationOrBindingElement(declaration *Node) bool {
	node := GetRootDeclaration(declaration)
	return node.Kind == KindVariableDeclaration && node.Parent.Kind == KindCatchClause
}

func IsTypeNodeKind(kind Kind) bool {
	switch kind {
	case KindAnyKeyword,
		KindUnknownKeyword,
		KindNumberKeyword,
		KindBigIntKeyword,
		KindObjectKeyword,
		KindBooleanKeyword,
		KindStringKeyword,
		KindSymbolKeyword,
		KindVoidKeyword,
		KindUndefinedKeyword,
		KindNeverKeyword,
		KindIntrinsicKeyword,
		KindExpressionWithTypeArguments,
		KindJSDocAllType,
		KindJSDocNullableType,
		KindJSDocNonNullableType,
		KindJSDocOptionalType,
		KindJSDocVariadicType:
		return true
	}
	return kind >= KindFirstTypeNode && kind <= KindLastTypeNode
}

func IsTypeNode(node *Node) bool {
	return IsTypeNodeKind(node.Kind)
}

func IsJSDocKind(kind Kind) bool {
	return KindFirstJSDocNode <= kind && kind <= KindLastJSDocNode
}

func IsJSDocTypeAssertion(node *Node) bool {
	if node == nil || !IsParenthesizedExpression(node) || !IsInJSFile(node) {
		return false
	}
	expr := node.Expression()
	return IsAsExpression(expr) && expr.Type() != nil && expr.Type().Flags&NodeFlagsReparsed != 0
}

func IsPrologueDirective(node *Node) bool {
	return node.Kind == KindExpressionStatement &&
		node.Expression().Kind == KindStringLiteral
}

type OuterExpressionKinds uint16

const (
	OEKParentheses                                       OuterExpressionKinds = 1 << 0
	OEKTypeAssertions                                    OuterExpressionKinds = 1 << 1
	OEKNonNullAssertions                                 OuterExpressionKinds = 1 << 2
	OEKPartiallyEmittedExpressions                       OuterExpressionKinds = 1 << 3
	OEKExpressionsWithTypeArguments                      OuterExpressionKinds = 1 << 4
	OEKSatisfies                                         OuterExpressionKinds = 1 << 5
	OEKExcludeJSDocTypeAssertion                         OuterExpressionKinds = 1 << 6
	OEKAssignments                                       OuterExpressionKinds = 1 << 7
	OEKComma                                             OuterExpressionKinds = 1 << 8
	OEKAssertions                                                             = OEKTypeAssertions | OEKNonNullAssertions | OEKSatisfies
	OEKAll                                                                    = OEKParentheses | OEKAssertions | OEKPartiallyEmittedExpressions | OEKExpressionsWithTypeArguments
	OEKAllExceptAssertionsOrExpressionsWithTypeArguments                      = OEKAll &^ OEKAssertions &^ OEKExpressionsWithTypeArguments
	OEKExpressionTypePassthrough                                              = OEKParentheses | OEKAssignments | OEKComma
)

// Determines whether node is an "outer expression" of the provided kinds
func IsOuterExpression(node *Expression, kinds OuterExpressionKinds) bool {
	switch node.Kind {
	case KindParenthesizedExpression:
		return kinds&OEKParentheses != 0 && !(kinds&OEKExcludeJSDocTypeAssertion != 0 && IsJSDocTypeAssertion(node))
	case KindTypeAssertionExpression, KindAsExpression:
		return kinds&OEKTypeAssertions != 0
	case KindSatisfiesExpression:
		return kinds&(OEKExpressionsWithTypeArguments|OEKSatisfies) != 0
	case KindExpressionWithTypeArguments:
		return kinds&OEKExpressionsWithTypeArguments != 0
	case KindNonNullExpression:
		return kinds&OEKNonNullAssertions != 0
	case KindPartiallyEmittedExpression:
		return kinds&OEKPartiallyEmittedExpressions != 0
	case KindBinaryExpression:
		switch node.AsBinaryExpression().OperatorToken.Kind {
		case KindEqualsToken:
			return kinds&OEKAssignments != 0
		case KindCommaToken:
			return kinds&OEKComma != 0
		}
	}
	return false
}

// Descends into an expression, skipping past "outer expressions" of the provided kinds
func SkipOuterExpressions(node *Expression, kinds OuterExpressionKinds) *Expression {
	for IsOuterExpression(node, kinds) {
		if IsBinaryExpression(node) {
			node = node.AsBinaryExpression().Right
		} else {
			node = node.Expression()
		}
	}
	return node
}

// Skips past the parentheses of an expression
func SkipParentheses(node *Expression) *Expression {
	return SkipOuterExpressions(node, OEKParentheses)
}

func SkipTypeParentheses(node *Node) *Node {
	for IsParenthesizedTypeNode(node) {
		node = node.Type()
	}
	return node
}

func SkipPartiallyEmittedExpressions(node *Expression) *Expression {
	return SkipOuterExpressions(node, OEKPartiallyEmittedExpressions)
}

// Walks up the parents of a parenthesized expression to find the containing node
func WalkUpParenthesizedExpressions(node *Expression) *Node {
	for node != nil && node.Kind == KindParenthesizedExpression {
		node = node.Parent
	}
	return node
}

// Walks up the parents of a parenthesized type to find the containing node
func WalkUpParenthesizedTypes(node *TypeNode) *Node {
	for node != nil && node.Kind == KindParenthesizedType {
		node = node.Parent
	}
	return node
}

// Walks up the parents of a node to find the containing SourceFile
func GetSourceFileOfNode(node *Node) *SourceFile {
	for node != nil {
		if node.Kind == KindSourceFile {
			return node.AsSourceFile()
		}
		node = node.Parent
	}
	return nil
}

var setParentInChildrenPool = sync.Pool{
	New: func() any {
		return newParentInChildrenSetter()
	},
}

func newParentInChildrenSetter() func(node *Node) bool {
	// Consolidate state into one allocation.
	// Similar to https://go.dev/cl/552375.
	var state struct {
		parent *Node
		visit  func(*Node) bool
	}

	state.visit = func(node *Node) bool {
		if state.parent != nil {
			node.Parent = state.parent
		}
		saveParent := state.parent
		state.parent = node
		node.ForEachChild(state.visit)
		state.parent = saveParent
		return false
	}

	return state.visit
}

func SetParentInChildren(node *Node) {
	fn := setParentInChildrenPool.Get().(func(node *Node) bool)
	defer setParentInChildrenPool.Put(fn)
	fn(node)
}

// This should never be called outside the parser
func SetImportsOfSourceFile(node *SourceFile, imports []*LiteralLikeNode) {
	node.imports = imports
}

// Walks up the parents of a node to find the ancestor that matches the callback
func FindAncestor(node *Node, callback func(*Node) bool) *Node {
	for node != nil {
		if callback(node) {
			return node
		}
		node = node.Parent
	}
	return nil
}

// Walks up the parents of a node to find the ancestor that matches the kind
func FindAncestorKind(node *Node, kind Kind) *Node {
	for node != nil {
		if node.Kind == kind {
			return node
		}
		node = node.Parent
	}
	return nil
}

type FindAncestorResult int32

const (
	FindAncestorFalse FindAncestorResult = iota
	FindAncestorTrue
	FindAncestorQuit
)

func ToFindAncestorResult(b bool) FindAncestorResult {
	if b {
		return FindAncestorTrue
	}
	return FindAncestorFalse
}

// Walks up the parents of a node to find the ancestor that matches the callback
func FindAncestorOrQuit(node *Node, callback func(*Node) FindAncestorResult) *Node {
	for node != nil {
		switch callback(node) {
		case FindAncestorQuit:
			return nil
		case FindAncestorTrue:
			return node
		}
		node = node.Parent
	}
	return nil
}

func IsNodeDescendantOf(node *Node, ancestor *Node) bool {
	for node != nil {
		if node == ancestor {
			return true
		}
		node = node.Parent
	}
	return false
}

func ModifierToFlag(token Kind) ModifierFlags {
	switch token {
	case KindStaticKeyword:
		return ModifierFlagsStatic
	case KindPublicKeyword:
		return ModifierFlagsPublic
	case KindProtectedKeyword:
		return ModifierFlagsProtected
	case KindPrivateKeyword:
		return ModifierFlagsPrivate
	case KindAbstractKeyword:
		return ModifierFlagsAbstract
	case KindAccessorKeyword:
		return ModifierFlagsAccessor
	case KindExportKeyword:
		return ModifierFlagsExport
	case KindDeclareKeyword:
		return ModifierFlagsAmbient
	case KindConstKeyword:
		return ModifierFlagsConst
	case KindDefaultKeyword:
		return ModifierFlagsDefault
	case KindAsyncKeyword:
		return ModifierFlagsAsync
	case KindReadonlyKeyword:
		return ModifierFlagsReadonly
	case KindOverrideKeyword:
		return ModifierFlagsOverride
	case KindInKeyword:
		return ModifierFlagsIn
	case KindOutKeyword:
		return ModifierFlagsOut
	case KindDecorator:
		return ModifierFlagsDecorator
	}
	return ModifierFlagsNone
}

func ModifiersToFlags(modifiers []*Node) ModifierFlags {
	var flags ModifierFlags
	for _, modifier := range modifiers {
		flags |= ModifierToFlag(modifier.Kind)
	}
	return flags
}

func HasSyntacticModifier(node *Node, flags ModifierFlags) bool {
	return node.ModifierFlags()&flags != 0
}

func HasAccessorModifier(node *Node) bool {
	return HasSyntacticModifier(node, ModifierFlagsAccessor)
}

func HasStaticModifier(node *Node) bool {
	return HasSyntacticModifier(node, ModifierFlagsStatic)
}

func IsStatic(node *Node) bool {
	// https://tc39.es/ecma262/#sec-static-semantics-isstatic
	return IsClassElement(node) && HasStaticModifier(node) || IsClassStaticBlockDeclaration(node)
}

func CanHaveSymbol(node *Node) bool {
	switch node.Kind {
	case KindArrowFunction, KindBinaryExpression, KindBindingElement, KindCallExpression, KindCallSignature,
		KindClassDeclaration, KindClassExpression, KindClassStaticBlockDeclaration, KindConstructor, KindConstructorType,
		KindConstructSignature, KindElementAccessExpression, KindEnumDeclaration, KindEnumMember, KindExportAssignment,
		KindExportDeclaration, KindExportSpecifier, KindFunctionDeclaration, KindFunctionExpression, KindFunctionType,
		KindGetAccessor, KindImportClause, KindImportEqualsDeclaration, KindImportSpecifier, KindIndexSignature,
		KindInterfaceDeclaration, KindJSTypeAliasDeclaration,
		KindJsxAttribute, KindJsxAttributes, KindJsxSpreadAttribute, KindMappedType, KindMethodDeclaration,
		KindMethodSignature, KindModuleDeclaration, KindNamedTupleMember, KindNamespaceExport, KindNamespaceExportDeclaration,
		KindNamespaceImport, KindNewExpression, KindNoSubstitutionTemplateLiteral, KindNumericLiteral, KindObjectLiteralExpression,
		KindParameter, KindPropertyAccessExpression, KindPropertyAssignment, KindPropertyDeclaration, KindPropertySignature,
		KindSetAccessor, KindShorthandPropertyAssignment, KindSourceFile, KindSpreadAssignment, KindStringLiteral,
		KindTypeAliasDeclaration, KindTypeLiteral, KindTypeParameter, KindVariableDeclaration:
		return true
	}
	return false
}

func CanHaveIllegalDecorators(node *Node) bool {
	switch node.Kind {
	case KindPropertyAssignment, KindShorthandPropertyAssignment,
		KindFunctionDeclaration, KindConstructor,
		KindIndexSignature, KindClassStaticBlockDeclaration,
		KindMissingDeclaration, KindVariableStatement,
		KindInterfaceDeclaration, KindTypeAliasDeclaration,
		KindEnumDeclaration, KindModuleDeclaration,
		KindImportEqualsDeclaration, KindImportDeclaration, KindJSImportDeclaration,
		KindNamespaceExportDeclaration, KindExportDeclaration,
		KindExportAssignment:
		return true
	}
	return false
}

func CanHaveIllegalModifiers(node *Node) bool {
	switch node.Kind {
	case KindClassStaticBlockDeclaration,
		KindPropertyAssignment,
		KindShorthandPropertyAssignment,
		KindMissingDeclaration,
		KindNamespaceExportDeclaration:
		return true
	}
	return false
}

func CanHaveModifiers(node *Node) bool {
	switch node.Kind {
	case KindTypeParameter,
		KindParameter,
		KindPropertySignature,
		KindPropertyDeclaration,
		KindMethodSignature,
		KindMethodDeclaration,
		KindConstructor,
		KindGetAccessor,
		KindSetAccessor,
		KindIndexSignature,
		KindConstructorType,
		KindFunctionExpression,
		KindArrowFunction,
		KindClassExpression,
		KindVariableStatement,
		KindFunctionDeclaration,
		KindClassDeclaration,
		KindInterfaceDeclaration,
		KindTypeAliasDeclaration,
		KindEnumDeclaration,
		KindModuleDeclaration,
		KindImportEqualsDeclaration,
		KindImportDeclaration,
		KindJSImportDeclaration,
		KindExportAssignment,
		KindExportDeclaration:
		return true
	}
	return false
}

func CanHaveDecorators(node *Node) bool {
	switch node.Kind {
	case KindParameter,
		KindPropertyDeclaration,
		KindMethodDeclaration,
		KindGetAccessor,
		KindSetAccessor,
		KindClassExpression,
		KindClassDeclaration:
		return true
	}
	return false
}

func IsFunctionOrModuleBlock(node *Node) bool {
	return IsSourceFile(node) || IsModuleBlock(node) || IsBlock(node) && IsFunctionLike(node.Parent)
}

func IsFunctionExpressionOrArrowFunction(node *Node) bool {
	return IsFunctionExpression(node) || IsArrowFunction(node)
}

// Warning: This has the same semantics as the forEach family of functions in that traversal terminates
// in the event that 'visitor' returns true.
func ForEachReturnStatement(body *Node, visitor func(stmt *Node) bool) bool {
	var traverse func(*Node) bool
	traverse = func(node *Node) bool {
		switch node.Kind {
		case KindReturnStatement:
			return visitor(node)
		case KindCaseBlock, KindBlock, KindIfStatement, KindDoStatement, KindWhileStatement, KindForStatement, KindForInStatement,
			KindForOfStatement, KindWithStatement, KindSwitchStatement, KindCaseClause, KindDefaultClause, KindLabeledStatement,
			KindTryStatement, KindCatchClause:
			return node.ForEachChild(traverse)
		}
		return false
	}
	return traverse(body)
}

func GetRootDeclaration(node *Node) *Node {
	for node.Kind == KindBindingElement {
		node = node.Parent.Parent
	}
	return node
}

func getCombinedFlags[T ~uint32](node *Node, getFlags func(*Node) T) T {
	node = GetRootDeclaration(node)
	flags := getFlags(node)
	if node.Kind == KindVariableDeclaration {
		node = node.Parent
	}
	if node != nil && node.Kind == KindVariableDeclarationList {
		flags |= getFlags(node)
		node = node.Parent
	}
	if node != nil && node.Kind == KindVariableStatement {
		flags |= getFlags(node)
	}
	return flags
}

func GetCombinedModifierFlags(node *Node) ModifierFlags {
	return getCombinedFlags(node, (*Node).ModifierFlags)
}

func GetCombinedNodeFlags(node *Node) NodeFlags {
	return getCombinedFlags(node, getNodeFlags)
}

func getNodeFlags(node *Node) NodeFlags {
	return node.Flags
}

// Gets whether a bound `VariableDeclaration` or `VariableDeclarationList` is part of an `await using` declaration.
func IsVarAwaitUsing(node *Node) bool {
	return GetCombinedNodeFlags(node)&NodeFlagsBlockScoped == NodeFlagsAwaitUsing
}

// Gets whether a bound `VariableDeclaration` or `VariableDeclarationList` is part of a `using` declaration.
func IsVarUsing(node *Node) bool {
	return GetCombinedNodeFlags(node)&NodeFlagsBlockScoped == NodeFlagsUsing
}

// GetJSDocDeprecatedTag returns the first @deprecated JSDoc tag for the given node, or nil if none exists.
func GetJSDocDeprecatedTag(node *Node) *Node {
	for _, jsdoc := range node.JSDoc(nil) {
		tags := jsdoc.AsJSDoc().Tags
		if tags != nil {
			for _, tag := range tags.Nodes {
				if IsJSDocDeprecatedTag(tag) {
					return tag
				}
			}
		}
	}
	return nil
}

// IsDeprecatedDeclaration reports whether the given declaration is marked as @deprecated.
// It checks NodeFlagsPossiblyContainsDeprecatedTag on combined node flags, then confirms
// by walking up to find the node with the flag and performing a JSDoc lookup.
func IsDeprecatedDeclaration(declaration *Node) bool {
	return IsDeprecatedDeclarationWithCachedFlags(declaration, GetCombinedNodeFlags(declaration))
}

// IsDeprecatedDeclarationWithCachedFlags is the core logic for IsDeprecatedDeclaration,
// parameterized on pre-computed combined flags so the checker can supply cached flags.
func IsDeprecatedDeclarationWithCachedFlags(declaration *Node, combinedFlags NodeFlags) bool {
	if combinedFlags&NodeFlagsPossiblyContainsDeprecatedTag == 0 {
		return false
	}
	// Walk up to find the node that directly has the flag, since JSDoc is
	// attached to that node (e.g. VariableStatement, not VariableDeclaration).
	for n := declaration; n != nil; n = n.Parent {
		if n.Flags&NodeFlagsPossiblyContainsDeprecatedTag != 0 {
			return GetJSDocDeprecatedTag(n) != nil
		}
	}
	return false
}

// Gets whether a bound `VariableDeclaration` or `VariableDeclarationList` is part of a `const` declaration.
func IsVarConst(node *Node) bool {
	return GetCombinedNodeFlags(node)&NodeFlagsBlockScoped == NodeFlagsConst
}

// Gets whether a bound `VariableDeclaration` or `VariableDeclarationList` is part of a `const`, `using` or `await using` declaration.
func IsVarConstLike(node *Node) bool {
	switch GetCombinedNodeFlags(node) & NodeFlagsBlockScoped {
	case NodeFlagsConst, NodeFlagsUsing, NodeFlagsAwaitUsing:
		return true
	}
	return false
}

// Gets whether a bound `VariableDeclaration` or `VariableDeclarationList` is part of a `let` declaration.
func IsVarLet(node *Node) bool {
	return GetCombinedNodeFlags(node)&NodeFlagsBlockScoped == NodeFlagsLet
}

func IsImportMeta(node *Node) bool {
	if node.Kind == KindMetaProperty {
		return node.AsMetaProperty().KeywordToken == KindImportKeyword && node.AsMetaProperty().Name().Text() == "meta"
	}
	return false
}

func WalkUpBindingElementsAndPatterns(binding *Node) *Node {
	node := binding.Parent
	for IsBindingElement(node.Parent) {
		node = node.Parent.Parent
	}
	return node.Parent
}

func IsSourceFileJS(file *SourceFile) bool {
	return file.ScriptKind == core.ScriptKindJS || file.ScriptKind == core.ScriptKindJSX
}

func IsInJSFile(node *Node) bool {
	return node != nil && node.Flags&NodeFlagsJavaScriptFile != 0
}

func IsDeclaration(node *Node) bool {
	if node.Kind == KindTypeParameter {
		return node.Parent != nil
	}
	return IsDeclarationNode(node)
}

// True if `name` is the name of a declaration node
func IsDeclarationName(name *Node) bool {
	return !IsSourceFile(name) && !IsBindingPattern(name) && IsDeclaration(name.Parent) && name.Parent.Name() == name
}

// Like 'isDeclarationName', but returns true for LHS of `import { x as y }` or `export { x as y }`.
func IsDeclarationNameOrImportPropertyName(name *Node) bool {
	switch name.Parent.Kind {
	case KindImportSpecifier, KindExportSpecifier:
		return IsIdentifier(name) || name.Kind == KindStringLiteral
	default:
		return IsDeclarationName(name)
	}
}

func IsLiteralComputedPropertyDeclarationName(node *Node) bool {
	return IsStringOrNumericLiteralLike(node) &&
		node.Parent.Kind == KindComputedPropertyName &&
		IsDeclaration(node.Parent.Parent)
}

func IsExternalModuleImportEqualsDeclaration(node *Node) bool {
	return node.Kind == KindImportEqualsDeclaration && node.AsImportEqualsDeclaration().ModuleReference.Kind == KindExternalModuleReference
}

func IsModuleOrEnumDeclaration(node *Node) bool {
	return node.Kind == KindModuleDeclaration || node.Kind == KindEnumDeclaration
}

func IsLiteralImportTypeNode(node *Node) bool {
	return IsImportTypeNode(node) && IsLiteralTypeNode(node.AsImportTypeNode().Argument) && IsStringLiteral(node.AsImportTypeNode().Argument.AsLiteralTypeNode().Literal)
}

func IsJsxTagName(node *Node) bool {
	parent := node.Parent
	switch parent.Kind {
	case KindJsxOpeningElement, KindJsxClosingElement, KindJsxSelfClosingElement:
		return parent.TagName() == node
	}
	return false
}

func IsImportOrExportSpecifier(node *Node) bool {
	return IsImportSpecifier(node) || IsExportSpecifier(node)
}

func IsVoidZero(node *Node) bool {
	return IsVoidExpression(node) && IsNumericLiteral(node.Expression()) && node.Expression().Text() == "0"
}

func IsExportsIdentifier(node *Node) bool {
	return IsIdentifier(node) && node.Text() == "exports"
}

func IsModuleIdentifier(node *Node) bool {
	return IsIdentifier(node) && node.Text() == "module"
}

func IsThisIdentifier(node *Node) bool {
	return IsIdentifier(node) && node.Text() == "this"
}

func IsThisParameter(node *Node) bool {
	return IsParameterDeclaration(node) && node.Name() != nil && IsThisIdentifier(node.Name())
}

func IsBindableStaticAccessExpression(node *Node, excludeThisKeyword bool) bool {
	return IsPropertyAccessExpression(node) &&
		(!excludeThisKeyword && node.Expression().Kind == KindThisKeyword || IsIdentifier(node.Name()) && IsBindableStaticNameExpression(node.Expression(), true /*excludeThisKeyword*/)) ||
		IsBindableStaticElementAccessExpression(node, excludeThisKeyword)
}

func IsBindableStaticElementAccessExpression(node *Node, excludeThisKeyword bool) bool {
	return IsLiteralLikeElementAccess(node) &&
		((!excludeThisKeyword && node.Expression().Kind == KindThisKeyword) ||
			IsEntityNameExpression(node.Expression()) ||
			IsBindableStaticAccessExpression(node.Expression(), true /*excludeThisKeyword*/))
}

func IsPrototypeAccess(node *Node) bool {
	if IsBindableStaticAccessExpression(node, false /*excludeThisKeyword*/) {
		if name := GetElementOrPropertyAccessName(node); name != nil {
			return name.Text() == "prototype"
		}
	}
	return false
}

func IsLiteralLikeElementAccess(node *Node) bool {
	return IsElementAccessExpression(node) && IsStringOrNumericLiteralLike(node.AsElementAccessExpression().ArgumentExpression)
}

func IsBindableStaticNameExpression(node *Node, excludeThisKeyword bool) bool {
	return IsEntityNameExpression(node) || IsBindableStaticAccessExpression(node, excludeThisKeyword)
}

// Does not handle signed numeric names like `a[+0]` - handling those would require handling prefix unary expressions
// throughout late binding handling as well, which is awkward (but ultimately probably doable if there is demand)
func GetElementOrPropertyAccessName(node *Node) *Node {
	switch node.Kind {
	case KindPropertyAccessExpression:
		if IsIdentifier(node.Name()) {
			return node.Name()
		}
		return nil
	case KindElementAccessExpression:
		if arg := SkipParentheses(node.AsElementAccessExpression().ArgumentExpression); IsStringOrNumericLiteralLike(arg) {
			return arg
		}
		return nil
	}
	panic("Unhandled case in GetElementOrPropertyAccessName")
}

func GetInitializerOfBinaryExpression(expr *BinaryExpression) *Expression {
	for IsBinaryExpression(expr.Right) {
		expr = expr.Right.AsBinaryExpression()
	}
	return expr.Right.Expression()
}

func IsExpressionWithTypeArgumentsInClassExtendsClause(node *Node) bool {
	return TryGetClassExtendingExpressionWithTypeArguments(node) != nil
}

func TryGetClassExtendingExpressionWithTypeArguments(node *Node) *ClassLikeDeclaration {
	cls, isImplements := TryGetClassImplementingOrExtendingExpressionWithTypeArguments(node)
	if cls != nil && !isImplements {
		return cls
	}
	return nil
}

func TryGetClassImplementingOrExtendingExpressionWithTypeArguments(node *Node) (class *ClassLikeDeclaration, isImplements bool) {
	if IsExpressionWithTypeArguments(node) {
		if IsHeritageClause(node.Parent) && IsClassLike(node.Parent.Parent) {
			return node.Parent.Parent, node.Parent.AsHeritageClause().Token == KindImplementsKeyword
		}
	}
	return nil, false
}

func GetNameOfDeclaration(declaration *Node) *Node {
	if declaration == nil {
		return nil
	}
	nonAssignedName := GetNonAssignedNameOfDeclaration(declaration)
	if nonAssignedName != nil {
		return nonAssignedName
	}
	if IsFunctionExpression(declaration) || IsArrowFunction(declaration) || IsClassExpression(declaration) {
		return GetAssignedName(declaration)
	}
	return nil
}

func GetNonAssignedNameOfDeclaration(declaration *Node) *Node {
	// !!!
	switch declaration.Kind {
	case KindBinaryExpression, KindCallExpression:
		switch GetAssignmentDeclarationKind(declaration) {
		case JSDeclarationKindProperty, JSDeclarationKindThisProperty, JSDeclarationKindExportsProperty:
			left := declaration.AsBinaryExpression().Left
			if name := GetElementOrPropertyAccessName(left); name != nil {
				return name
			}
			return left
		case JSDeclarationKindObjectDefinePropertyValue, JSDeclarationKindObjectDefinePropertyExports:
			return declaration.Arguments()[1]
		}
		return nil
	case KindExportAssignment:
		expr := declaration.Expression()
		if IsIdentifier(expr) {
			return expr
		}
		return nil
	}
	return declaration.Name()
}

func GetAssignedName(node *Node) *Node {
	parent := node.Parent
	if parent != nil {
		switch parent.Kind {
		case KindPropertyAssignment:
			return parent.AsPropertyAssignment().Name()
		case KindBindingElement:
			return parent.AsBindingElement().Name()
		case KindBinaryExpression:
			if node == parent.AsBinaryExpression().Right {
				left := parent.AsBinaryExpression().Left
				switch left.Kind {
				case KindIdentifier:
					return left
				case KindPropertyAccessExpression:
					return left.AsPropertyAccessExpression().Name()
				case KindElementAccessExpression:
					arg := SkipParentheses(left.AsElementAccessExpression().ArgumentExpression)
					if IsStringOrNumericLiteralLike(arg) {
						return arg
					}
				}
			}
		case KindVariableDeclaration:
			name := parent.AsVariableDeclaration().Name()
			if IsIdentifier(name) {
				return name
			}
		}
	}
	return nil
}

type JSDeclarationKind int

const (
	JSDeclarationKindNone JSDeclarationKind = iota
	// module.exports = expr, except for module.exports = exports
	JSDeclarationKindModuleExports
	// exports.name = expr
	// module.exports.name = expr
	JSDeclarationKindExportsProperty
	// this.name = expr
	JSDeclarationKindThisProperty
	// F.name = expr, F[name] = expr, in JS or TS file
	JSDeclarationKindProperty
	// Object.defineProperty(x, 'name', { value: any, writable?: boolean (false by default) });
	// Object.defineProperty(x, 'name', { get: Function, set: Function });
	// Object.defineProperty(x, 'name', { get: Function });
	// Object.defineProperty(x, 'name', { set: Function });
	JSDeclarationKindObjectDefinePropertyValue
	// Object.defineProperty(exports || module.exports, 'name', ...);
	JSDeclarationKindObjectDefinePropertyExports
)

func GetAssignmentDeclarationKind(node *Node) JSDeclarationKind {
	switch node.Kind {
	case KindBinaryExpression:
		bin := node.AsBinaryExpression()
		if bin.OperatorToken.Kind == KindEqualsToken && IsAccessExpression(bin.Left) {
			if IsInJSFile(bin.Left) {
				if IsModuleExportsAccessExpression(bin.Left) && !IsExportsIdentifier(bin.Right) {
					return JSDeclarationKindModuleExports
				}
				if (IsModuleExportsAccessExpression(bin.Left.Expression()) || IsExportsIdentifier(bin.Left.Expression())) &&
					GetElementOrPropertyAccessName(bin.Left) != nil {
					return JSDeclarationKindExportsProperty
				}
				if bin.Left.Expression().Kind == KindThisKeyword {
					return JSDeclarationKindThisProperty
				}
			}
			if bin.Left.Kind == KindPropertyAccessExpression && IsEntityNameExpressionEx(bin.Left.Expression(), IsInJSFile(bin.Left)) && IsIdentifier(bin.Left.Name()) ||
				bin.Left.Kind == KindElementAccessExpression && IsEntityNameExpressionEx(bin.Left.Expression(), IsInJSFile(bin.Left)) {
				return JSDeclarationKindProperty
			}
		}
	case KindCallExpression:
		if IsInJSFile(node) && IsBindableObjectDefinePropertyCall(node) {
			entityName := node.Arguments()[0]
			if IsExportsIdentifier(entityName) || IsModuleExportsAccessExpression(entityName) {
				return JSDeclarationKindObjectDefinePropertyExports
			}
			return JSDeclarationKindObjectDefinePropertyValue
		}
	}
	return JSDeclarationKindNone
}

func IsBindableObjectDefinePropertyCall(node *Node) bool {
	if args := node.Arguments(); len(args) == 3 {
		if expr := node.Expression(); IsPropertyAccessExpression(expr) &&
			IsIdentifier(expr.Expression()) && expr.Expression().Text() == "Object" &&
			expr.Name().Text() == "defineProperty" &&
			IsStringOrNumericLiteralLike(args[1]) &&
			IsBindableStaticNameExpression(args[0] /*excludeThisKeyword*/, true) {
			return true
		}
	}
	return false
}

/**
 * A declaration has a dynamic name if all of the following are true:
 *   1. The declaration has a computed property name.
 *   2. The computed name is *not* expressed as a StringLiteral.
 *   3. The computed name is *not* expressed as a NumericLiteral.
 *   4. The computed name is *not* expressed as a PlusToken or MinusToken
 *      immediately followed by a NumericLiteral.
 */
func HasDynamicName(declaration *Node) bool {
	name := GetNameOfDeclaration(declaration)
	return name != nil && IsDynamicName(name)
}

func IsDynamicName(name *Node) bool {
	var expr *Node
	switch name.Kind {
	case KindComputedPropertyName:
		expr = name.Expression()
	case KindElementAccessExpression:
		expr = SkipParentheses(name.AsElementAccessExpression().ArgumentExpression)
	default:
		return false
	}
	return !IsStringOrNumericLiteralLike(expr) && !IsSignedNumericLiteral(expr)
}

func IsEntityNameExpression(node *Node) bool {
	return IsEntityNameExpressionEx(node, false /*allowJS*/)
}

func IsEntityNameExpressionEx(node *Node, allowJS bool) bool {
	return IsIdentifier(node) ||
		IsPropertyAccessEntityNameExpression(node, allowJS) ||
		allowJS && (node.Kind == KindThisKeyword || isElementAccessEntityNameExpression(node, allowJS))
}

func IsPropertyAccessEntityNameExpression(node *Node, allowJS bool) bool {
	return IsPropertyAccessExpression(node) && IsIdentifier(node.Name()) && IsEntityNameExpressionEx(node.Expression(), allowJS)
}

func isElementAccessEntityNameExpression(node *Node, allowJS bool) bool {
	return IsElementAccessExpression(node) && IsStringOrNumericLiteralLike(node.AsElementAccessExpression().ArgumentExpression) && IsEntityNameExpressionEx(node.Expression(), allowJS)
}

func IsDottedName(node *Node) bool {
	switch node.Kind {
	case KindIdentifier, KindThisKeyword, KindSuperKeyword, KindMetaProperty:
		return true
	case KindPropertyAccessExpression, KindParenthesizedExpression:
		return IsDottedName(node.Expression())
	}
	return false
}

func HasSamePropertyAccessName(node1, node2 *Node) bool {
	if node1.Kind == KindIdentifier && node2.Kind == KindIdentifier {
		return node1.Text() == node2.Text()
	} else if node1.Kind == KindPropertyAccessExpression && node2.Kind == KindPropertyAccessExpression {
		return node1.AsPropertyAccessExpression().Name().Text() == node2.AsPropertyAccessExpression().Name().Text() &&
			HasSamePropertyAccessName(node1.Expression(), node2.Expression())
	}
	return false
}

func IsAmbientModule(node *Node) bool {
	return IsModuleDeclaration(node) && (node.AsModuleDeclaration().Name().Kind == KindStringLiteral || IsGlobalScopeAugmentation(node))
}

func IsAmbientModuleSymbolName(s string) bool {
	return strings.HasPrefix(s, "\"") && strings.HasSuffix(s, "\"")
}

func IsExternalModule(file *SourceFile) bool {
	return file.ExternalModuleIndicator != nil
}

func IsExternalOrCommonJSModule(file *SourceFile) bool {
	return file.ExternalModuleIndicator != nil || file.CommonJSModuleIndicator != nil
}

// TODO: Should we deprecate `IsExternalOrCommonJSModule` in favor of this function?
func IsEffectiveExternalModule(node *SourceFile, compilerOptions *core.CompilerOptions) bool {
	return IsExternalModule(node) || (isCommonJSContainingModuleKind(compilerOptions.GetEmitModuleKind()) && node.CommonJSModuleIndicator != nil)
}

func isCommonJSContainingModuleKind(kind core.ModuleKind) bool {
	return kind == core.ModuleKindCommonJS || core.ModuleKindNode16 <= kind && kind <= core.ModuleKindNodeNext
}

func IsExternalModuleIndicator(node *Statement) bool {
	// Exported top-level member indicates moduleness
	return IsAnyImportOrReExport(node) || IsExportAssignment(node) || HasSyntacticModifier(node, ModifierFlagsExport)
}

func IsExportNamespaceAsDefaultDeclaration(node *Node) bool {
	if IsExportDeclaration(node) {
		decl := node.AsExportDeclaration()
		return IsNamespaceExport(decl.ExportClause) && ModuleExportNameIsDefault(decl.ExportClause.Name())
	}
	return false
}

func IsGlobalScopeAugmentation(node *Node) bool {
	return IsModuleDeclaration(node) && node.AsModuleDeclaration().Keyword == KindGlobalKeyword
}

func IsModuleAugmentationExternal(node *Node) bool {
	// external module augmentation is a ambient module declaration that is either:
	// - defined in the top level scope and source file is an external module
	// - defined inside ambient module declaration located in the top level scope and source file not an external module
	switch node.Parent.Kind {
	case KindSourceFile:
		return IsExternalModule(node.Parent.AsSourceFile())
	case KindModuleBlock:
		grandParent := node.Parent.Parent
		return IsAmbientModule(grandParent) && IsSourceFile(grandParent.Parent) && !IsExternalModule(grandParent.Parent.AsSourceFile())
	}
	return false
}

func IsModuleWithStringLiteralName(node *Node) bool {
	return IsModuleDeclaration(node) && node.Name().Kind == KindStringLiteral
}

func GetContainingClass(node *Node) *Node {
	return FindAncestor(node.Parent, IsClassLike)
}

func GetExtendsHeritageClauseElement(node *Node) *ExpressionWithTypeArgumentsNode {
	return core.FirstOrNil(GetExtendsHeritageClauseElements(node))
}

func GetExtendsHeritageClauseElements(node *Node) []*ExpressionWithTypeArgumentsNode {
	return GetHeritageElements(node, KindExtendsKeyword)
}

func GetImplementsHeritageClauseElements(node *Node) []*ExpressionWithTypeArgumentsNode {
	return GetHeritageElements(node, KindImplementsKeyword)
}

func GetHeritageElements(node *Node, kind Kind) []*Node {
	clause := GetHeritageClause(node, kind)
	if clause != nil {
		return clause.AsHeritageClause().Types.Nodes
	}
	return nil
}

func GetHeritageClause(node *Node, kind Kind) *Node {
	clauses := getHeritageClauses(node)
	if clauses != nil {
		for _, clause := range clauses.Nodes {
			if clause.AsHeritageClause().Token == kind {
				return clause
			}
		}
	}
	return nil
}

func getHeritageClauses(node *Node) *NodeList {
	switch node.Kind {
	case KindClassDeclaration:
		return node.AsClassDeclaration().HeritageClauses
	case KindClassExpression:
		return node.AsClassExpression().HeritageClauses
	case KindInterfaceDeclaration:
		return node.AsInterfaceDeclaration().HeritageClauses
	}
	return nil
}

func IsPartOfTypeQuery(node *Node) bool {
	for node.Kind == KindQualifiedName || node.Kind == KindIdentifier {
		node = node.Parent
	}
	return node.Kind == KindTypeQuery
}

/**
 * This function returns true if the this node's root declaration is a parameter.
 * For example, passing a `ParameterDeclaration` will return true, as will passing a
 * binding element that is a child of a `ParameterDeclaration`.
 *
 * If you are looking to test that a `Node` is a `ParameterDeclaration`, use `isParameter`.
 */
func IsPartOfParameterDeclaration(node *Node) bool {
	return GetRootDeclaration(node).Kind == KindParameter
}

func IsInTopLevelContext(node *Node) bool {
	// The name of a class or function declaration is a BindingIdentifier in its surrounding scope.
	if IsIdentifier(node) {
		parent := node.Parent
		if (IsClassDeclaration(parent) || IsFunctionDeclaration(parent)) && parent.Name() == node {
			node = parent
		}
	}
	container := GetThisContainer(node, true /*includeArrowFunctions*/, false /*includeClassComputedPropertyName*/)
	return IsSourceFile(container)
}

func GetThisContainer(node *Node, includeArrowFunctions bool, includeClassComputedPropertyName bool) *Node {
	for {
		node = node.Parent
		if node == nil {
			panic("nil parent in getThisContainer")
		}
		switch node.Kind {
		case KindComputedPropertyName:
			if includeClassComputedPropertyName && IsClassLike(node.Parent.Parent) {
				return node
			}
			node = node.Parent.Parent
		case KindDecorator:
			if node.Parent.Kind == KindParameter && IsClassElement(node.Parent.Parent) {
				// If the decorator's parent is a ParameterDeclaration, we resolve the this container from
				// the grandparent class declaration.
				node = node.Parent.Parent
			} else if IsClassElement(node.Parent) {
				// If the decorator's parent is a class element, we resolve the 'this' container
				// from the parent class declaration.
				node = node.Parent
			}
		case KindArrowFunction:
			if includeArrowFunctions {
				return node
			}
		case KindFunctionDeclaration, KindFunctionExpression, KindModuleDeclaration, KindClassStaticBlockDeclaration,
			KindPropertyDeclaration, KindPropertySignature, KindMethodDeclaration, KindMethodSignature, KindConstructor,
			KindGetAccessor, KindSetAccessor, KindCallSignature, KindConstructSignature, KindIndexSignature,
			KindEnumDeclaration, KindSourceFile:
			return node
		}
	}
}

func GetSuperContainer(node *Node, stopOnFunctions bool) *Node {
	for node = node.Parent; node != nil; node = node.Parent {
		switch node.Kind {
		case KindComputedPropertyName:
			node = node.Parent
		case KindFunctionDeclaration, KindFunctionExpression, KindArrowFunction:
			if !stopOnFunctions {
				continue
			}
			return node
		case KindPropertyDeclaration, KindPropertySignature, KindMethodDeclaration, KindMethodSignature, KindConstructor, KindGetAccessor, KindSetAccessor, KindClassStaticBlockDeclaration:
			return node
		case KindDecorator:
			// Decorators are always applied outside of the body of a class or method.
			if node.Parent.Kind == KindParameter && IsClassElement(node.Parent.Parent) {
				// If the decorator's parent is a ParameterDeclaration, we resolve the this container from
				// the grandparent class declaration.
				node = node.Parent.Parent
			} else if IsClassElement(node.Parent) {
				// If the decorator's parent is a class element, we resolve the 'this' container
				// from the parent class declaration.
				node = node.Parent
			}
		}
	}
	return nil
}

func GetImmediatelyInvokedFunctionExpression(fn *Node) *Node {
	if IsFunctionExpressionOrArrowFunction(fn) {
		prev := fn
		parent := fn.Parent
		for IsParenthesizedExpression(parent) {
			prev = parent
			parent = parent.Parent
		}
		if IsCallExpression(parent) && parent.Expression() == prev {
			return parent
		}
	}
	return nil
}

func IsEnumConst(node *Node) bool {
	return GetCombinedModifierFlags(node)&ModifierFlagsConst != 0
}

func ExpressionIsAlias(node *Node) bool {
	return IsEntityNameExpression(node) || IsClassExpression(node)
}

func IsInstanceOfExpression(node *Node) bool {
	return IsBinaryExpression(node) && node.AsBinaryExpression().OperatorToken.Kind == KindInstanceOfKeyword
}

func IsAnyImportOrReExport(node *Node) bool {
	return IsImportNode(node) || IsExportDeclaration(node)
}

func IsImportNode(node *Node) bool {
	return IsAnyImportSyntax(node) || NodeKindIs(node, KindJSImportDeclaration)
}

// Checks if the node is a genuine import declation. In particular the re-parsed KindJSImportDeclaration
// is explicitly excluded because the callers of this function are typically not prepared to handle it properly.
// For more permissive check, use IsImportNode.
func IsAnyImportSyntax(node *Node) bool {
	return NodeKindIs(node, KindImportDeclaration, KindImportEqualsDeclaration)
}

func IsJsonSourceFile(file *SourceFile) bool {
	return file.ScriptKind == core.ScriptKindJSON
}

func IsInJsonFile(node *Node) bool {
	return node.Flags&NodeFlagsJsonFile != 0
}

func GetExternalModuleName(node *Node) *Expression {
	switch node.Kind {
	case KindImportDeclaration, KindJSImportDeclaration, KindExportDeclaration:
		return node.ModuleSpecifier()
	case KindImportEqualsDeclaration:
		if node.AsImportEqualsDeclaration().ModuleReference.Kind == KindExternalModuleReference {
			return node.AsImportEqualsDeclaration().ModuleReference.Expression()
		}
		return nil
	case KindImportType:
		return getImportTypeNodeLiteral(node)
	case KindCallExpression:
		return core.FirstOrNil(node.Arguments())
	case KindModuleDeclaration:
		if IsStringLiteral(node.AsModuleDeclaration().Name()) {
			return node.AsModuleDeclaration().Name()
		}
		return nil
	}
	panic("Unhandled case in getExternalModuleName")
}

func GetImportAttributes(node *Node) *Node {
	switch node.Kind {
	case KindImportDeclaration, KindJSImportDeclaration:
		return node.AsImportDeclaration().Attributes
	case KindExportDeclaration:
		return node.AsExportDeclaration().Attributes
	}
	panic("Unhandled case in getImportAttributes")
}

func getImportTypeNodeLiteral(node *Node) *Node {
	if IsImportTypeNode(node) {
		importTypeNode := node.AsImportTypeNode()
		if IsLiteralTypeNode(importTypeNode.Argument) {
			literalTypeNode := importTypeNode.Argument.AsLiteralTypeNode()
			if IsStringLiteral(literalTypeNode.Literal) {
				return literalTypeNode.Literal
			}
		}
	}
	return nil
}

func IsExpressionNode(node *Node) bool {
	switch node.Kind {
	case KindSuperKeyword, KindNullKeyword, KindTrueKeyword, KindFalseKeyword, KindRegularExpressionLiteral,
		KindArrayLiteralExpression, KindObjectLiteralExpression, KindPropertyAccessExpression, KindElementAccessExpression,
		KindCallExpression, KindNewExpression, KindTaggedTemplateExpression, KindAsExpression, KindTypeAssertionExpression,
		KindSatisfiesExpression, KindNonNullExpression, KindParenthesizedExpression, KindFunctionExpression,
		KindClassExpression, KindArrowFunction, KindVoidExpression, KindDeleteExpression, KindTypeOfExpression,
		KindPrefixUnaryExpression, KindPostfixUnaryExpression, KindBinaryExpression, KindConditionalExpression,
		KindSpreadElement, KindTemplateExpression, KindOmittedExpression, KindJsxElement, KindJsxSelfClosingElement,
		KindJsxFragment, KindYieldExpression, KindAwaitExpression:
		return true
	case KindMetaProperty:
		// `import.defer` in `import.defer(...)` is not an expression
		return !IsImportCall(node.Parent) || node.Parent.Expression() != node
	case KindExpressionWithTypeArguments:
		return !IsHeritageClause(node.Parent)
	case KindQualifiedName:
		for node.Parent.Kind == KindQualifiedName {
			node = node.Parent
		}
		return IsTypeQueryNode(node.Parent) || IsJSDocLinkLike(node.Parent) || IsJSDocNameReference(node.Parent) || IsJsxTagName(node)
	case KindPrivateIdentifier:
		return IsBinaryExpression(node.Parent) && node.Parent.AsBinaryExpression().Left == node && node.Parent.AsBinaryExpression().OperatorToken.Kind == KindInKeyword
	case KindIdentifier:
		if IsTypeQueryNode(node.Parent) || IsJSDocLinkLike(node.Parent) || IsJSDocNameReference(node.Parent) || IsJsxTagName(node) {
			return true
		}
		fallthrough
	case KindNumericLiteral, KindBigIntLiteral, KindStringLiteral, KindNoSubstitutionTemplateLiteral, KindThisKeyword:
		return IsInExpressionContext(node)
	default:
		return false
	}
}

func IsInExpressionContext(node *Node) bool {
	parent := node.Parent
	switch parent.Kind {
	case KindVariableDeclaration, KindParameter, KindPropertyDeclaration, KindPropertySignature, KindEnumMember, KindPropertyAssignment, KindBindingElement:
		return parent.Initializer() == node
	case KindExpressionStatement, KindIfStatement, KindDoStatement, KindWhileStatement, KindReturnStatement, KindWithStatement, KindSwitchStatement,
		KindCaseClause, KindDefaultClause, KindThrowStatement, KindTypeAssertionExpression, KindAsExpression, KindTemplateSpan, KindComputedPropertyName,
		KindSatisfiesExpression:
		return parent.Expression() == node
	case KindForStatement:
		s := parent.AsForStatement()
		return s.Initializer == node && s.Initializer.Kind != KindVariableDeclarationList || s.Condition == node || s.Incrementor == node
	case KindForInStatement, KindForOfStatement:
		s := parent.AsForInOrOfStatement()
		return s.Initializer == node && s.Initializer.Kind != KindVariableDeclarationList || s.Expression == node
	case KindDecorator, KindJsxExpression, KindJsxSpreadAttribute, KindSpreadAssignment:
		return true
	case KindExpressionWithTypeArguments:
		return parent.Expression() == node && !IsPartOfTypeNode(parent)
	case KindShorthandPropertyAssignment:
		return parent.AsShorthandPropertyAssignment().ObjectAssignmentInitializer == node
	default:
		return IsExpressionNode(parent)
	}
}

func IsPartOfTypeNode(node *Node) bool {
	kind := node.Kind
	if kind >= KindFirstTypeNode && kind <= KindLastTypeNode {
		return true
	}
	switch node.Kind {
	case KindAnyKeyword, KindUnknownKeyword, KindNumberKeyword, KindBigIntKeyword, KindStringKeyword,
		KindBooleanKeyword, KindSymbolKeyword, KindObjectKeyword, KindUndefinedKeyword, KindNullKeyword,
		KindNeverKeyword:
		return true
	case KindVoidKeyword:
		return node.Parent.Kind != KindVoidExpression
	case KindExpressionWithTypeArguments:
		return isPartOfTypeExpressionWithTypeArguments(node)
	case KindTypeParameter:
		return node.Parent.Kind == KindMappedType || node.Parent.Kind == KindInferType
	case KindIdentifier:
		parent := node.Parent
		if IsQualifiedName(parent) && parent.AsQualifiedName().Right == node {
			return isPartOfTypeNodeInParent(parent)
		}
		if IsPropertyAccessExpression(parent) && parent.AsPropertyAccessExpression().Name() == node {
			return isPartOfTypeNodeInParent(parent)
		}
		return isPartOfTypeNodeInParent(node)
	case KindQualifiedName, KindPropertyAccessExpression, KindThisKeyword:
		return isPartOfTypeNodeInParent(node)
	}
	return false
}

func isPartOfTypeNodeInParent(node *Node) bool {
	parent := node.Parent
	if parent.Kind == KindTypeQuery {
		return false
	}
	if parent.Kind == KindImportType {
		return !parent.AsImportTypeNode().IsTypeOf
	}

	// Do not recursively call isPartOfTypeNode on the parent. In the example:
	//
	//     let a: A.B.C;
	//
	// Calling isPartOfTypeNode would consider the qualified name A.B a type node.
	// Only C and A.B.C are type nodes.
	if parent.Kind >= KindFirstTypeNode && parent.Kind <= KindLastTypeNode {
		return true
	}
	switch parent.Kind {
	case KindExpressionWithTypeArguments:
		return isPartOfTypeExpressionWithTypeArguments(parent)
	case KindTypeParameter:
		return node == parent.AsTypeParameterDeclaration().Constraint
	case KindVariableDeclaration, KindParameter, KindPropertyDeclaration, KindPropertySignature, KindFunctionDeclaration,
		KindFunctionExpression, KindArrowFunction, KindConstructor, KindMethodDeclaration, KindMethodSignature,
		KindGetAccessor, KindSetAccessor, KindCallSignature, KindConstructSignature, KindIndexSignature,
		KindTypeAssertionExpression:
		return node == parent.Type()
	case KindCallExpression, KindNewExpression, KindTaggedTemplateExpression:
		return slices.Contains(parent.TypeArguments(), node)
	}
	return false
}

func isPartOfTypeExpressionWithTypeArguments(node *Node) bool {
	parent := node.Parent
	return IsHeritageClause(parent) && (!IsClassLike(parent.Parent) || parent.AsHeritageClause().Token == KindImplementsKeyword) ||
		IsJSDocImplementsTag(parent) ||
		IsJSDocAugmentsTag(parent)
}

func IsJSDocLinkLike(node *Node) bool {
	return NodeKindIs(node, KindJSDocLink, KindJSDocLinkCode, KindJSDocLinkPlain)
}

func IsJSDocTag(node *Node) bool {
	return node.Kind >= KindFirstJSDocTagNode && node.Kind <= KindLastJSDocTagNode
}

func IsSuperCall(node *Node) bool {
	return IsCallExpression(node) && node.Expression().Kind == KindSuperKeyword
}

func IsImportCall(node *Node) bool {
	if !IsCallExpression(node) {
		return false
	}
	e := node.Expression()
	return e.Kind == KindImportKeyword || IsMetaProperty(e) && e.AsMetaProperty().KeywordToken == KindImportKeyword && e.Text() == "defer"
}

func IsComputedNonLiteralName(name *Node) bool {
	return IsComputedPropertyName(name) && !IsStringOrNumericLiteralLike(name.Expression())
}

func IsQuestionToken(node *Node) bool {
	return node != nil && node.Kind == KindQuestionToken
}

func EntityNameToString(name *Node, getTextOfNode func(*Node) string) string {
	switch name.Kind {
	case KindThisKeyword:
		return "this"
	case KindIdentifier, KindPrivateIdentifier:
		if NodeIsSynthesized(name) || getTextOfNode == nil {
			return name.Text()
		}
		return getTextOfNode(name)
	case KindQualifiedName:
		return EntityNameToString(name.AsQualifiedName().Left, getTextOfNode) + "." + EntityNameToString(name.AsQualifiedName().Right, getTextOfNode)
	case KindPropertyAccessExpression:
		return EntityNameToString(name.Expression(), getTextOfNode) + "." + EntityNameToString(name.AsPropertyAccessExpression().Name(), getTextOfNode)
	case KindJsxNamespacedName:
		return EntityNameToString(name.AsJsxNamespacedName().Namespace, getTextOfNode) + ":" + EntityNameToString(name.AsJsxNamespacedName().Name(), getTextOfNode)
	}
	panic("Unhandled case in EntityNameToString")
}

func GetTextOfPropertyName(name *Node) string {
	text, _ := TryGetTextOfPropertyName(name)
	return text
}

func TryGetTextOfPropertyName(name *Node) (string, bool) {
	switch name.Kind {
	case KindIdentifier, KindPrivateIdentifier, KindStringLiteral, KindNumericLiteral, KindBigIntLiteral,
		KindNoSubstitutionTemplateLiteral:
		return name.Text(), true
	case KindComputedPropertyName:
		if IsStringOrNumericLiteralLike(name.Expression()) {
			return name.Expression().Text(), true
		}
	case KindJsxNamespacedName:
		return name.AsJsxNamespacedName().Namespace.Text() + ":" + name.Name().Text(), true
	}
	return "", false
}

func IsJSDocNode(node *Node) bool {
	return node.Kind >= KindFirstJSDocNode && node.Kind <= KindLastJSDocNode
}

func IsNonWhitespaceToken(node *Node) bool {
	return IsTokenKind(node.Kind) && !IsWhitespaceOnlyJsxText(node)
}

func IsWhitespaceOnlyJsxText(node *Node) bool {
	return node.Kind == KindJsxText && node.AsJsxText().ContainsOnlyTriviaWhiteSpaces
}

func GetNewTargetContainer(node *Node) *Node {
	container := GetThisContainer(node, false /*includeArrowFunctions*/, false /*includeClassComputedPropertyName*/)
	if container != nil {
		switch container.Kind {
		case KindConstructor, KindFunctionDeclaration, KindFunctionExpression:
			return container
		}
	}
	return nil
}

func GetEnclosingBlockScopeContainer(node *Node) *Node {
	return FindAncestor(node.Parent, func(current *Node) bool {
		return IsBlockScope(current, current.Parent)
	})
}

func IsBlockScope(node *Node, parentNode *Node) bool {
	switch node.Kind {
	case KindSourceFile, KindCaseBlock, KindCatchClause, KindModuleDeclaration, KindForStatement, KindForInStatement, KindForOfStatement,
		KindConstructor, KindMethodDeclaration, KindGetAccessor, KindSetAccessor, KindFunctionDeclaration, KindFunctionExpression,
		KindArrowFunction, KindPropertyDeclaration, KindClassStaticBlockDeclaration:
		return true
	case KindBlock:
		// function block is not considered block-scope container
		// see comment in binder.ts: bind(...), case for SyntaxKind.Block
		return !IsFunctionLikeOrClassStaticBlockDeclaration(parentNode)
	}
	return false
}

type SemanticMeaning int32

const (
	SemanticMeaningNone      SemanticMeaning = 0
	SemanticMeaningValue     SemanticMeaning = 1 << 0
	SemanticMeaningType      SemanticMeaning = 1 << 1
	SemanticMeaningNamespace SemanticMeaning = 1 << 2
	SemanticMeaningAll       SemanticMeaning = SemanticMeaningValue | SemanticMeaningType | SemanticMeaningNamespace
)

func GetMeaningFromDeclaration(node *Node) SemanticMeaning {
	switch node.Kind {
	case KindVariableDeclaration:
		return SemanticMeaningValue
	case KindParameter,
		KindBindingElement,
		KindPropertyDeclaration,
		KindPropertySignature,
		KindPropertyAssignment,
		KindShorthandPropertyAssignment,
		KindMethodDeclaration,
		KindMethodSignature,
		KindConstructor,
		KindGetAccessor,
		KindSetAccessor,
		KindFunctionDeclaration,
		KindFunctionExpression,
		KindArrowFunction,
		KindCatchClause,
		KindJsxAttribute:
		return SemanticMeaningValue

	case KindTypeParameter,
		KindInterfaceDeclaration,
		KindTypeAliasDeclaration,
		KindJSTypeAliasDeclaration,
		KindTypeLiteral:
		return SemanticMeaningType
	case KindEnumMember, KindClassDeclaration:
		return SemanticMeaningValue | SemanticMeaningType

	case KindModuleDeclaration:
		if IsAmbientModule(node) {
			return SemanticMeaningNamespace | SemanticMeaningValue
		} else if GetModuleInstanceState(node) == ModuleInstanceStateInstantiated {
			return SemanticMeaningNamespace | SemanticMeaningValue
		} else {
			return SemanticMeaningNamespace
		}

	case KindEnumDeclaration,
		KindNamedImports,
		KindImportSpecifier,
		KindImportEqualsDeclaration,
		KindImportDeclaration,
		KindJSImportDeclaration,
		KindExportAssignment,
		KindExportDeclaration:
		return SemanticMeaningAll

	// An external module can be a Value
	case KindSourceFile:
		return SemanticMeaningNamespace | SemanticMeaningValue
	}

	return SemanticMeaningAll
}

func IsPropertyAccessOrQualifiedName(node *Node) bool {
	return node.Kind == KindPropertyAccessExpression || node.Kind == KindQualifiedName
}

func IsLabelName(node *Node) bool {
	return IsLabelOfLabeledStatement(node) || IsJumpStatementTarget(node)
}

func IsLabelOfLabeledStatement(node *Node) bool {
	if !IsIdentifier(node) {
		return false
	}
	if !IsLabeledStatement(node.Parent) {
		return false
	}
	return node == node.Parent.Label()
}

func IsJumpStatementTarget(node *Node) bool {
	if !IsIdentifier(node) {
		return false
	}
	if !IsBreakOrContinueStatement(node.Parent) {
		return false
	}
	return node == node.Parent.Label()
}

func IsBreakOrContinueStatement(node *Node) bool {
	return NodeKindIs(node, KindBreakStatement, KindContinueStatement)
}

// GetModuleInstanceState is used during binding as well as in transformations and tests, and therefore may be invoked
// with a node that does not yet have its `Parent` pointer set. In this case, an `ancestors` represents a stack of
// virtual `Parent` pointers that can be used to walk up the tree. Since `getModuleInstanceStateForAliasTarget` may
// potentially walk up out of the provided `Node`, merely setting the parent pointers for a given `ModuleDeclaration`
// prior to invoking `GetModuleInstanceState` is not sufficient. It is, however, necessary that the `Parent` pointers
// for all ancestors of the `Node` provided to `GetModuleInstanceState` have been set.

// Push a virtual parent pointer onto `ancestors` and return it.
func pushAncestor(ancestors []*Node, parent *Node) []*Node {
	return append(ancestors, parent)
}

// If a virtual `Parent` exists on the stack, returns the previous stack entry and the virtual `Parent“.
// Otherwise, we return `nil` and the value of `node.Parent`.
func popAncestor(ancestors []*Node, node *Node) ([]*Node, *Node) {
	if len(ancestors) == 0 {
		return nil, node.Parent
	}
	n := len(ancestors) - 1
	return ancestors[:n], ancestors[n]
}

type ModuleInstanceState int32

const (
	ModuleInstanceStateUnknown ModuleInstanceState = iota
	ModuleInstanceStateNonInstantiated
	ModuleInstanceStateInstantiated
	ModuleInstanceStateConstEnumOnly
)

func GetModuleInstanceState(node *Node) ModuleInstanceState {
	return getModuleInstanceState(node, nil, nil)
}

func getModuleInstanceState(node *Node, ancestors []*Node, visited map[NodeId]ModuleInstanceState) ModuleInstanceState {
	module := node.AsModuleDeclaration()
	if module.Body != nil {
		return getModuleInstanceStateCached(module.Body, pushAncestor(ancestors, node), visited)
	} else {
		return ModuleInstanceStateInstantiated
	}
}

func getModuleInstanceStateCached(node *Node, ancestors []*Node, visited map[NodeId]ModuleInstanceState) ModuleInstanceState {
	if visited == nil {
		visited = make(map[NodeId]ModuleInstanceState)
	}
	nodeId := GetNodeId(node)
	if cached, ok := visited[nodeId]; ok {
		if cached != ModuleInstanceStateUnknown {
			return cached
		}
		return ModuleInstanceStateNonInstantiated
	}
	visited[nodeId] = ModuleInstanceStateUnknown
	result := getModuleInstanceStateWorker(node, ancestors, visited)
	visited[nodeId] = result
	return result
}

func getModuleInstanceStateWorker(node *Node, ancestors []*Node, visited map[NodeId]ModuleInstanceState) ModuleInstanceState {
	// A module is uninstantiated if it contains only
	switch node.Kind {
	case KindInterfaceDeclaration, KindTypeAliasDeclaration, KindJSTypeAliasDeclaration:
		return ModuleInstanceStateNonInstantiated
	case KindEnumDeclaration:
		if IsEnumConst(node) {
			return ModuleInstanceStateConstEnumOnly
		}
	case KindImportDeclaration, KindJSImportDeclaration, KindImportEqualsDeclaration:
		if !HasSyntacticModifier(node, ModifierFlagsExport) {
			return ModuleInstanceStateNonInstantiated
		}
	case KindExportDeclaration:
		decl := node.AsExportDeclaration()
		if decl.ModuleSpecifier == nil && decl.ExportClause != nil && decl.ExportClause.Kind == KindNamedExports {
			state := ModuleInstanceStateNonInstantiated
			ancestors = pushAncestor(ancestors, node)
			ancestors = pushAncestor(ancestors, decl.ExportClause)
			for _, specifier := range decl.ExportClause.Elements() {
				specifierState := getModuleInstanceStateForAliasTarget(specifier, ancestors, visited)
				if specifierState > state {
					state = specifierState
				}
				if state == ModuleInstanceStateInstantiated {
					return state
				}
			}
			return state
		}
	case KindModuleBlock:
		state := ModuleInstanceStateNonInstantiated
		ancestors = pushAncestor(ancestors, node)
		node.ForEachChild(func(n *Node) bool {
			childState := getModuleInstanceStateCached(n, ancestors, visited)
			switch childState {
			case ModuleInstanceStateNonInstantiated:
				return false
			case ModuleInstanceStateConstEnumOnly:
				state = ModuleInstanceStateConstEnumOnly
				return false
			case ModuleInstanceStateInstantiated:
				state = ModuleInstanceStateInstantiated
				return true
			}
			panic("Unhandled case in getModuleInstanceStateWorker")
		})
		return state
	case KindModuleDeclaration:
		return getModuleInstanceState(node, ancestors, visited)
	}
	return ModuleInstanceStateInstantiated
}

func getModuleInstanceStateForAliasTarget(node *Node, ancestors []*Node, visited map[NodeId]ModuleInstanceState) ModuleInstanceState {
	name := node.PropertyNameOrName()
	if name.Kind != KindIdentifier {
		// Skip for invalid syntax like this: export { "x" }
		return ModuleInstanceStateInstantiated
	}
	for ancestors, p := popAncestor(ancestors, node); p != nil; ancestors, p = popAncestor(ancestors, p) {
		if IsBlock(p) || IsModuleBlock(p) || IsSourceFile(p) {
			found := ModuleInstanceStateUnknown
			statementsAncestors := pushAncestor(ancestors, p)
			for _, statement := range p.Statements() {
				if NodeHasName(statement, name) {
					state := getModuleInstanceStateCached(statement, statementsAncestors, visited)
					if found == ModuleInstanceStateUnknown || state > found {
						found = state
					}
					if found == ModuleInstanceStateInstantiated {
						return found
					}
					if statement.Kind == KindImportEqualsDeclaration {
						// Treat re-exports of import aliases as instantiated since they're ambiguous. This is consistent
						// with `export import x = mod.x` being treated as instantiated:
						//   import x = mod.x;
						//   export { x };
						found = ModuleInstanceStateInstantiated
					}
				}
			}
			if found != ModuleInstanceStateUnknown {
				return found
			}
		}
	}
	// Couldn't locate, assume could refer to a value
	return ModuleInstanceStateInstantiated
}

func IsInstantiatedModule(node *Node, preserveConstEnums bool) bool {
	moduleState := GetModuleInstanceState(node)
	return moduleState == ModuleInstanceStateInstantiated ||
		(preserveConstEnums && moduleState == ModuleInstanceStateConstEnumOnly)
}

func NodeHasName(statement *Node, id *Node) bool {
	name := statement.Name()
	if name != nil {
		return IsIdentifier(name) && name.Text() == id.Text()
	}
	if IsVariableStatement(statement) {
		declarations := statement.AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes
		return core.Some(declarations, func(d *Node) bool { return NodeHasName(d, id) })
	}
	return false
}

func IsInternalModuleImportEqualsDeclaration(node *Node) bool {
	return IsImportEqualsDeclaration(node) && node.AsImportEqualsDeclaration().ModuleReference.Kind != KindExternalModuleReference
}

func IsConstAssertion(node *Node) bool {
	switch node.Kind {
	case KindAsExpression, KindTypeAssertionExpression:
		return IsConstTypeReference(node.Type())
	}
	return false
}

func IsConstTypeReference(node *Node) bool {
	return IsTypeReferenceNode(node) && len(node.TypeArguments()) == 0 && IsIdentifier(node.AsTypeReferenceNode().TypeName) && node.AsTypeReferenceNode().TypeName.Text() == "const"
}

func IsGlobalSourceFile(node *Node) bool {
	return node.Kind == KindSourceFile && !IsExternalOrCommonJSModule(node.AsSourceFile())
}

func IsParameterLike(node *Node) bool {
	switch node.Kind {
	case KindParameter, KindTypeParameter:
		return true
	}
	return false
}

func GetDeclarationOfKind(symbol *Symbol, kind Kind) *Node {
	for _, declaration := range symbol.Declarations {
		if declaration.Kind == kind {
			return declaration
		}
	}
	return nil
}

func FindConstructorDeclaration(node *ClassLikeDeclaration) *Node {
	for _, member := range node.Members() {
		if IsConstructorDeclaration(member) && NodeIsPresent(member.Body()) {
			return member
		}
	}
	return nil
}

func GetFirstIdentifier(node *Node) *Node {
	switch node.Kind {
	case KindIdentifier:
		return node
	case KindQualifiedName:
		return GetFirstIdentifier(node.AsQualifiedName().Left)
	case KindPropertyAccessExpression:
		return GetFirstIdentifier(node.AsPropertyAccessExpression().Expression)
	}
	panic("Unhandled case in GetFirstIdentifier")
}

func GetNamespaceDeclarationNode(node *Node) *Node {
	switch node.Kind {
	case KindImportDeclaration, KindJSImportDeclaration:
		importClause := node.ImportClause()
		if importClause != nil && importClause.AsImportClause().NamedBindings != nil && IsNamespaceImport(importClause.AsImportClause().NamedBindings) {
			return importClause.AsImportClause().NamedBindings
		}
	case KindImportEqualsDeclaration:
		return node
	case KindExportDeclaration:
		exportClause := node.AsExportDeclaration().ExportClause
		if exportClause != nil && IsNamespaceExport(exportClause) {
			return exportClause
		}
	default:
		panic("Unhandled case in getNamespaceDeclarationNode")
	}
	return nil
}

func ModuleExportNameIsDefault(node *Node) bool {
	return node.Text() == InternalSymbolNameDefault
}

func IsDefaultImport(node *Node /*ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration*/) bool {
	switch node.Kind {
	case KindImportDeclaration, KindJSImportDeclaration:
		importClause := node.ImportClause()
		return importClause != nil && importClause.AsImportClause().name != nil
	}
	return false
}

func GetImpliedNodeFormatForFile(path string, packageJsonType string) core.ModuleKind {
	impliedNodeFormat := core.ResolutionModeNone
	if tspath.FileExtensionIsOneOf(path, []string{tspath.ExtensionDmts, tspath.ExtensionMts, tspath.ExtensionMjs}) {
		impliedNodeFormat = core.ResolutionModeESM
	} else if tspath.FileExtensionIsOneOf(path, []string{tspath.ExtensionDcts, tspath.ExtensionCts, tspath.ExtensionCjs}) {
		impliedNodeFormat = core.ResolutionModeCommonJS
	} else if tspath.FileExtensionIsOneOf(path, []string{tspath.ExtensionDts, tspath.ExtensionTs, tspath.ExtensionTsx, tspath.ExtensionJs, tspath.ExtensionJsx}) {
		impliedNodeFormat = core.IfElse(packageJsonType == "module", core.ResolutionModeESM, core.ResolutionModeCommonJS)
	}

	return impliedNodeFormat
}

func GetEmitModuleFormatOfFileWorker(fileName string, options *core.CompilerOptions, sourceFileMetaData SourceFileMetaData) core.ModuleKind {
	result := GetImpliedNodeFormatForEmitWorker(fileName, options.GetEmitModuleKind(), sourceFileMetaData)
	if result != core.ModuleKindNone {
		return result
	}
	return options.GetEmitModuleKind()
}

func GetImpliedNodeFormatForEmitWorker(fileName string, emitModuleKind core.ModuleKind, sourceFileMetaData SourceFileMetaData) core.ResolutionMode {
	if core.ModuleKindNode16 <= emitModuleKind && emitModuleKind <= core.ModuleKindNodeNext {
		return sourceFileMetaData.ImpliedNodeFormat
	}
	if sourceFileMetaData.ImpliedNodeFormat == core.ModuleKindCommonJS &&
		(sourceFileMetaData.PackageJsonType == "commonjs" ||
			tspath.FileExtensionIsOneOf(fileName, []string{tspath.ExtensionCjs, tspath.ExtensionCts})) {
		return core.ModuleKindCommonJS
	}
	if sourceFileMetaData.ImpliedNodeFormat == core.ModuleKindESNext &&
		(sourceFileMetaData.PackageJsonType == "module" ||
			tspath.FileExtensionIsOneOf(fileName, []string{tspath.ExtensionMjs, tspath.ExtensionMts})) {
		return core.ModuleKindESNext
	}
	return core.ModuleKindNone
}

func GetDeclarationContainer(node *Node) *Node {
	return FindAncestor(GetRootDeclaration(node), func(node *Node) bool {
		switch node.Kind {
		case KindVariableDeclaration,
			KindVariableDeclarationList,
			KindImportSpecifier,
			KindNamedImports,
			KindNamespaceImport,
			KindImportClause:
			return false
		default:
			return true
		}
	}).Parent
}

// Indicates that a symbol is an alias that does not merge with a local declaration.
// OR Is a JSContainer which may merge an alias with a local declaration
func IsNonLocalAlias(symbol *Symbol, excludes SymbolFlags) bool {
	if symbol == nil {
		return false
	}
	return symbol.Flags&(SymbolFlagsAlias|excludes) == SymbolFlagsAlias ||
		symbol.Flags&SymbolFlagsAlias != 0 && symbol.Flags&SymbolFlagsAssignment != 0
}

// An alias symbol is created by one of the following declarations:
//
//	import <symbol> = ...
//	const <symbol> = ... (JS only)
//	const { <symbol>, ... } = ... (JS only)
//	import <symbol> from ...
//	import * as <symbol> from ...
//	import { x as <symbol> } from ...
//	export { x as <symbol> } from ...
//	export * as ns <symbol> from ...
//	export = <EntityNameExpression>
//	export default <EntityNameExpression>
//	module.exports = <EntityNameExpression> (JS only)
//	module.exports.<symbol> = <EntityNameExpression> (JS only)
//	exports.<symbol> = <EntityNameExpression> (JS only)
func IsAliasSymbolDeclaration(node *Node) bool {
	switch node.Kind {
	case KindImportEqualsDeclaration, KindNamespaceExportDeclaration, KindNamespaceImport, KindNamespaceExport,
		KindImportSpecifier, KindExportSpecifier:
		return true
	case KindImportClause:
		return node.AsImportClause().Name() != nil
	case KindExportAssignment:
		return ExpressionIsAlias(node.Expression())
	case KindVariableDeclaration, KindBindingElement:
		return IsVariableDeclarationInitializedToRequire(node)
	case KindBinaryExpression:
		switch GetAssignmentDeclarationKind(node) {
		case JSDeclarationKindModuleExports, JSDeclarationKindExportsProperty:
			return ExpressionIsAlias(node.AsBinaryExpression().Right)
		}
	}
	return false
}

func IsParseTreeNode(node *Node) bool {
	return node.Flags&NodeFlagsSynthesized == 0
}

// Returns a token if position is in [start-of-leading-trivia, end), includes JSDoc only if requested
func GetNodeAtPosition(file *SourceFile, position int, includeJSDoc bool) *Node {
	current := file.AsNode()
	for {
		var child *Node
		if includeJSDoc {
			for _, jsdoc := range current.JSDoc(file) {
				if nodeContainsPosition(jsdoc, position) {
					child = jsdoc
					break
				}
			}
		}
		if child == nil {
			current.ForEachChild(func(node *Node) bool {
				if nodeContainsPosition(node, position) {
					child = node
					return true
				}
				return false
			})
		}
		if child == nil || IsMetaProperty(child) {
			return current
		}
		current = child
	}
}

func nodeContainsPosition(node *Node, position int) bool {
	return node.Kind >= KindFirstNode && node.Pos() <= position && (position < node.End() || position == node.End() && node.Kind == KindEndOfFile)
}

func findImportOrRequire(text string, start int) (index int, size int) {
	index = max(start, 0)
	n := len(text)
	for index < n {
		next := strings.IndexAny(text[index:], "ir")
		if next < 0 {
			break
		}
		index += next

		var expected string
		if text[index] == 'i' {
			size = 6
			expected = "import"
		} else {
			size = 7
			expected = "require"
		}
		if index+size <= n && text[index:index+size] == expected {
			return index, size
		}
		index++
	}

	return -1, 0
}

func ForEachDynamicImportOrRequireCall(
	file *SourceFile,
	includeTypeSpaceImports bool,
	requireStringLiteralLikeArgument bool,
	cb func(node *Node, argument *Expression) bool,
) bool {
	isJavaScriptFile := IsInJSFile(file.AsNode())
	lastIndex, size := findImportOrRequire(file.Text(), 0)
	for lastIndex >= 0 {
		node := GetNodeAtPosition(file, lastIndex, isJavaScriptFile && includeTypeSpaceImports)
		if isJavaScriptFile && IsRequireCall(node, requireStringLiteralLikeArgument) {
			if cb(node, node.Arguments()[0]) {
				return true
			}
		} else if IsImportCall(node) && len(node.Arguments()) > 0 && (!requireStringLiteralLikeArgument || IsStringLiteralLike(node.Arguments()[0])) {
			if cb(node, node.Arguments()[0]) {
				return true
			}
		} else if includeTypeSpaceImports && IsLiteralImportTypeNode(node) {
			if cb(node, node.AsImportTypeNode().Argument.AsLiteralTypeNode().Literal) {
				return true
			}
		}
		// skip past import/require
		lastIndex += size
		lastIndex, size = findImportOrRequire(file.Text(), lastIndex)
	}
	return false
}

// Returns true if the node is a CallExpression to the identifier 'require' with
// exactly one argument (of the form 'require("name")').
// This function does not test if the node is in a JavaScript file or not.
func IsRequireCall(node *Node, requireStringLiteralLikeArgument bool) bool {
	if !IsCallExpression(node) {
		return false
	}
	call := node.AsCallExpression()
	if !IsIdentifier(call.Expression) || call.Expression.Text() != "require" {
		return false
	}
	if len(call.Arguments.Nodes) != 1 {
		return false
	}
	return !requireStringLiteralLikeArgument || IsStringLiteralLike(call.Arguments.Nodes[0])
}

func IsRequireVariableStatement(node *Node) bool {
	if IsVariableStatement(node) {
		if declarations := node.AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes; len(declarations) > 0 {
			return core.Every(declarations, IsVariableDeclarationInitializedToRequire)
		}
	}
	return false
}

func GetJSXImplicitImportBase(compilerOptions *core.CompilerOptions, file *SourceFile) string {
	jsxImportSourcePragma := GetPragmaFromSourceFile(file, "jsximportsource")
	jsxRuntimePragma := GetPragmaFromSourceFile(file, "jsxruntime")
	if GetPragmaArgument(jsxRuntimePragma, "factory") == "classic" {
		return ""
	}
	if compilerOptions.Jsx == core.JsxEmitReactJSX ||
		compilerOptions.Jsx == core.JsxEmitReactJSXDev ||
		compilerOptions.JsxImportSource != "" ||
		jsxImportSourcePragma != nil ||
		GetPragmaArgument(jsxRuntimePragma, "factory") == "automatic" {
		result := GetPragmaArgument(jsxImportSourcePragma, "factory")
		if result == "" {
			result = compilerOptions.JsxImportSource
		}
		if result == "" {
			result = "react"
		}
		return result
	}
	return ""
}

func GetJSXRuntimeImport(base string, options *core.CompilerOptions) string {
	if base == "" {
		return base
	}
	return base + "/" + core.IfElse(options.Jsx == core.JsxEmitReactJSXDev, "jsx-dev-runtime", "jsx-runtime")
}

func GetPragmaFromSourceFile(file *SourceFile, name string) *Pragma {
	var result *Pragma
	if file != nil {
		for i := range file.Pragmas {
			if file.Pragmas[i].Name == name {
				result = &file.Pragmas[i] // Last one wins
			}
		}
	}
	return result
}

func GetPragmaArgument(pragma *Pragma, name string) string {
	if pragma != nil {
		if arg, ok := pragma.Args[name]; ok {
			return arg.Value
		}
	}
	return ""
}

// Of the form: `const x = require("x")` or `const { x } = require("x")` or with `var` or `let`
// The variable must not be exported and must not have a type annotation, even a jsdoc one.
// The initializer must be a call to `require` with a string literal or a string literal-like argument.
func IsVariableDeclarationInitializedToRequire(node *Node) bool {
	if node.Kind == KindBindingElement {
		node = node.Parent.Parent
	}
	return isVariableDeclarationInitializedWithRequireHelper(node, false /*allowAccessedRequire*/)
}

func IsVariableDeclarationInitializedToBareOrAccessedRequire(node *Node) bool {
	return isVariableDeclarationInitializedWithRequireHelper(node, true /*allowAccessedRequire*/)
}

func isVariableDeclarationInitializedWithRequireHelper(node *Node, allowAccessedRequire bool) bool {
	if !IsInJSFile(node) {
		return false
	}
	if node.Kind != KindVariableDeclaration {
		return false
	}
	initializer := node.Initializer()
	if initializer == nil {
		return false
	}
	if allowAccessedRequire {
		initializer = GetLeftmostAccessExpression(initializer)
	}

	return node.Parent.Parent.ModifierFlags()&ModifierFlagsExport == 0 &&
		node.Type() == nil &&
		IsRequireCall(initializer, true /*requireStringLiteralLikeArgument*/)
}

func GetModuleSpecifierOfBareOrAccessedRequire(node *Node) *Node {
	if isVariableDeclarationInitializedWithRequireHelper(node, false /*allowAccessedRequire*/) {
		return node.Initializer().Arguments()[0]
	}
	if isVariableDeclarationInitializedWithRequireHelper(node, true /*allowAccessedRequire*/) {
		leftmost := GetLeftmostAccessExpression(node.Initializer())
		if IsRequireCall(leftmost, true /*requireStringLiteralLikeArgument*/) {
			return leftmost.Arguments()[0]
		}
	}
	return nil
}

func IsModuleExportsAccessExpression(node *Node) bool {
	if IsAccessExpression(node) && IsModuleIdentifier(node.Expression()) {
		if name := GetElementOrPropertyAccessName(node); name != nil {
			return name.Text() == "exports"
		}
	}
	return false
}

func IsModuleExportsQualifiedName(node *Node) bool {
	return IsQualifiedName(node) && IsModuleIdentifier(node.AsQualifiedName().Left) && node.AsQualifiedName().Right.Text() == "exports"
}

func IsCheckJSEnabledForFile(sourceFile *SourceFile, compilerOptions *core.CompilerOptions) bool {
	if sourceFile.CheckJsDirective != nil {
		return sourceFile.CheckJsDirective.Enabled
	}
	return compilerOptions.CheckJs == core.TSTrue
}

func IsPlainJSFile(file *SourceFile, checkJs core.Tristate) bool {
	return file != nil && (file.ScriptKind == core.ScriptKindJS || file.ScriptKind == core.ScriptKindJSX) && file.CheckJsDirective == nil && checkJs == core.TSUnknown
}

func GetLeftmostAccessExpression(expr *Node) *Node {
	for IsAccessExpression(expr) {
		expr = expr.Expression()
	}
	return expr
}

func IsTypeOnlyImportDeclaration(node *Node) bool {
	switch node.Kind {
	case KindImportSpecifier:
		return node.IsTypeOnly() || node.Parent.Parent.IsTypeOnly()
	case KindNamespaceImport:
		return node.Parent.IsTypeOnly()
	case KindImportClause, KindImportEqualsDeclaration:
		return node.IsTypeOnly()
	}
	return false
}

func isTypeOnlyExportDeclaration(node *Node) bool {
	switch node.Kind {
	case KindExportSpecifier:
		return node.IsTypeOnly() || node.Parent.Parent.IsTypeOnly()
	case KindExportDeclaration:
		d := node.AsExportDeclaration()
		return d.IsTypeOnly && d.ModuleSpecifier != nil && d.ExportClause == nil
	case KindNamespaceExport:
		return node.Parent.IsTypeOnly()
	}
	return false
}

func IsTypeOnlyImportOrExportDeclaration(node *Node) bool {
	return IsTypeOnlyImportDeclaration(node) || isTypeOnlyExportDeclaration(node)
}

func IsExclusivelyTypeOnlyImportOrExport(node *Node) bool {
	switch node.Kind {
	case KindExportDeclaration:
		return node.IsTypeOnly()
	case KindImportDeclaration, KindJSImportDeclaration:
		if importClause := node.ImportClause(); importClause != nil {
			return importClause.AsImportClause().IsTypeOnly()
		}
	case KindJSDocImportTag:
		if importClause := node.ImportClause(); importClause != nil {
			return importClause.AsImportClause().IsTypeOnly()
		}
	}
	return false
}

func GetClassLikeDeclarationOfSymbol(symbol *Symbol) *Node {
	return core.Find(symbol.Declarations, IsClassLike)
}

func IsCallLikeExpression(node *Node) bool {
	switch node.Kind {
	case KindJsxOpeningElement, KindJsxSelfClosingElement, KindJsxOpeningFragment, KindCallExpression, KindNewExpression,
		KindTaggedTemplateExpression, KindDecorator:
		return true
	case KindBinaryExpression:
		return node.AsBinaryExpression().OperatorToken.Kind == KindInstanceOfKeyword
	}
	return false
}

func IsJsxCallLike(node *Node) bool {
	switch node.Kind {
	case KindJsxOpeningElement, KindJsxSelfClosingElement, KindJsxOpeningFragment:
		return true
	}
	return false
}

func IsCallLikeOrFunctionLikeExpression(node *Node) bool {
	return IsCallLikeExpression(node) || IsFunctionExpressionOrArrowFunction(node)
}

func NodeHasKind(node *Node, kind Kind) bool {
	if node == nil {
		return false
	}
	return node.Kind == kind
}

func IsContextualKeyword(token Kind) bool {
	return KindFirstContextualKeyword <= token && token <= KindLastContextualKeyword
}

func IsThisInTypeQuery(node *Node) bool {
	if !IsThisIdentifier(node) {
		return false
	}
	for IsQualifiedName(node.Parent) && node.Parent.AsQualifiedName().Left == node {
		node = node.Parent
	}
	return node.Parent.Kind == KindTypeQuery
}

// Gets whether a bound `VariableDeclaration` or `VariableDeclarationList` is part of a `let` declaration.
func IsLet(node *Node) bool {
	return GetCombinedNodeFlags(node)&NodeFlagsBlockScoped == NodeFlagsLet
}

func IsClassMemberModifier(token Kind) bool {
	return IsParameterPropertyModifier(token) || token == KindStaticKeyword ||
		token == KindOverrideKeyword || token == KindAccessorKeyword
}

func IsParameterPropertyModifier(kind Kind) bool {
	return ModifierToFlag(kind)&ModifierFlagsParameterPropertyModifier != 0
}

func ForEachChildAndJSDoc(node *Node, sourceFile *SourceFile, v Visitor) bool {
	if visitNodes(v, node.JSDoc(sourceFile)) {
		return true
	}
	return node.ForEachChild(v)
}

func HasTypeArguments(node *Node) bool {
	switch node.Kind {
	case KindCallExpression, KindNewExpression, KindTaggedTemplateExpression,
		KindTypeReference, KindExpressionWithTypeArguments, KindImportType,
		KindTypeQuery, KindJsxOpeningElement, KindJsxSelfClosingElement:
		return true
	}
	return false
}

func IsTypeReferenceType(node *Node) bool {
	return node.Kind == KindTypeReference || node.Kind == KindExpressionWithTypeArguments
}

func IsVariableLike(node *Node) bool {
	switch node.Kind {
	case KindBindingElement, KindEnumMember, KindParameter, KindPropertyAssignment, KindPropertyDeclaration,
		KindPropertySignature, KindShorthandPropertyAssignment, KindVariableDeclaration:
		return true
	}
	return false
}

func HasInitializer(node *Node) bool {
	switch node.Kind {
	case KindVariableDeclaration, KindParameter, KindBindingElement, KindPropertyDeclaration,
		KindPropertyAssignment, KindEnumMember, KindForStatement, KindForInStatement, KindForOfStatement,
		KindJsxAttribute:
		return node.Initializer() != nil
	default:
		return false
	}
}

func IsVariableParameterOrProperty(node *Node) bool {
	switch node.Kind {
	case KindVariableDeclaration, KindParameter, KindPropertySignature, KindPropertyDeclaration:
		return true
	default:
		return false
	}
}

func GetTypeAnnotationNode(node *Node) *TypeNode {
	switch node.Kind {
	case KindVariableDeclaration, KindParameter, KindPropertySignature, KindPropertyDeclaration,
		KindTypePredicate, KindParenthesizedType, KindTypeOperator, KindMappedType, KindTypeAssertionExpression,
		KindAsExpression, KindSatisfiesExpression, KindTypeAliasDeclaration, KindJSTypeAliasDeclaration,
		KindNamedTupleMember, KindOptionalType, KindRestType, KindTemplateLiteralTypeSpan, KindJSDocTypeExpression,
		KindJSDocPropertyTag, KindJSDocNullableType, KindJSDocNonNullableType, KindJSDocOptionalType:
		return node.Type()
	default:
		funcLike := node.FunctionLikeData()
		if funcLike != nil {
			return funcLike.Type
		}
		return nil
	}
}

func IsObjectTypeDeclaration(node *Node) bool {
	return IsClassLike(node) || IsInterfaceDeclaration(node) || IsTypeLiteralNode(node)
}

func IsClassOrTypeElement(node *Node) bool {
	return IsClassElement(node) || IsTypeElement(node)
}

func GetClassExtendsHeritageElement(node *Node) *ExpressionWithTypeArgumentsNode {
	heritageElements := GetHeritageElements(node, KindExtendsKeyword)
	if len(heritageElements) > 0 {
		return heritageElements[0]
	}
	return nil
}

func GetImplementsTypeNodes(node *Node) []*ExpressionWithTypeArgumentsNode {
	return GetHeritageElements(node, KindImplementsKeyword)
}

func IsTypeKeywordToken(node *Node) bool {
	return node.Kind == KindTypeKeyword
}

// See `IsJSDocSingleCommentNode`.
func IsJSDocSingleCommentNodeList(nodeList *NodeList) bool {
	if nodeList == nil || len(nodeList.Nodes) == 0 {
		return false
	}
	parent := nodeList.Nodes[0].Parent
	if parent == nil {
		return false
	}
	return IsJSDocSingleCommentNode(parent) && nodeList == parent.CommentList()
}

// See `IsJSDocSingleCommentNode`.
func IsJSDocSingleCommentNodeComment(node *Node) bool {
	if node == nil || node.Parent == nil {
		return false
	}
	return IsJSDocSingleCommentNode(node.Parent) && node == node.Parent.CommentList().Nodes[0]
}

// In Strada, if a JSDoc node has a single comment, that comment is represented as a string property
// as a simplification, and therefore that comment is not visited by `forEachChild`.
func IsJSDocSingleCommentNode(node *Node) bool {
	return hasComment(node.Kind) && node.CommentList() != nil && len(node.CommentList().Nodes) == 1
}

func IsValidTypeOnlyAliasUseSite(useSite *Node) bool {
	return useSite.Flags&(NodeFlagsAmbient|NodeFlagsJSDoc) != 0 ||
		IsPartOfTypeQuery(useSite) ||
		isIdentifierInNonEmittingHeritageClause(useSite) ||
		isPartOfPossiblyValidTypeOrAbstractComputedPropertyName(useSite) ||
		!(IsExpressionNode(useSite) || isShorthandPropertyNameUseSite(useSite))
}

func isIdentifierInNonEmittingHeritageClause(node *Node) bool {
	if !IsIdentifier(node) {
		return false
	}
	parent := node.Parent
	for IsPropertyAccessExpression(parent) || IsExpressionWithTypeArguments(parent) {
		parent = parent.Parent
	}
	return IsHeritageClause(parent) && (parent.AsHeritageClause().Token == KindImplementsKeyword || IsInterfaceDeclaration(parent.Parent))
}

func isPartOfPossiblyValidTypeOrAbstractComputedPropertyName(node *Node) bool {
	for NodeKindIs(node, KindIdentifier, KindPropertyAccessExpression) {
		node = node.Parent
	}
	if node.Kind != KindComputedPropertyName {
		return false
	}
	if HasSyntacticModifier(node.Parent, ModifierFlagsAbstract) {
		return true
	}
	return NodeKindIs(node.Parent.Parent, KindInterfaceDeclaration, KindTypeLiteral)
}

func isShorthandPropertyNameUseSite(useSite *Node) bool {
	return IsIdentifier(useSite) && IsShorthandPropertyAssignment(useSite.Parent) && useSite.Parent.AsShorthandPropertyAssignment().Name() == useSite
}

func GetPropertyNameForPropertyNameNode(name *Node) string {
	switch name.Kind {
	case KindIdentifier, KindPrivateIdentifier, KindStringLiteral, KindNoSubstitutionTemplateLiteral,
		KindNumericLiteral, KindBigIntLiteral, KindJsxNamespacedName:
		return name.Text()
	case KindComputedPropertyName:
		nameExpression := name.Expression()
		if IsStringOrNumericLiteralLike(nameExpression) {
			return nameExpression.Text()
		}
		if IsSignedNumericLiteral(nameExpression) {
			text := nameExpression.AsPrefixUnaryExpression().Operand.Text()
			if nameExpression.AsPrefixUnaryExpression().Operator == KindMinusToken {
				text = "-" + text
			}
			return text
		}
		return InternalSymbolNameMissing
	}
	panic("Unhandled case in getPropertyNameForPropertyNameNode")
}

func IsPartOfTypeOnlyImportOrExportDeclaration(node *Node) bool {
	return FindAncestor(node, IsTypeOnlyImportOrExportDeclaration) != nil
}

func IsPartOfExclusivelyTypeOnlyImportOrExportDeclaration(node *Node) bool {
	return FindAncestor(node, IsExclusivelyTypeOnlyImportOrExport) != nil
}

func IsEmittableImport(node *Node) bool {
	switch node.Kind {
	case KindImportDeclaration:
		return node.ImportClause() != nil && !node.ImportClause().IsTypeOnly()
	case KindExportDeclaration, KindImportEqualsDeclaration:
		return !node.IsTypeOnly()
	case KindCallExpression:
		return IsImportCall(node)
	}
	return false
}

func IsResolutionModeOverrideHost(node *Node) bool {
	if node == nil {
		return false
	}
	switch node.Kind {
	case KindImportType, KindExportDeclaration, KindImportDeclaration, KindJSImportDeclaration:
		return true
	}
	return false
}

func HasResolutionModeOverride(node *Node) bool {
	if node == nil {
		return false
	}
	var attributes *ImportAttributesNode
	switch node.Kind {
	case KindImportType:
		attributes = node.AsImportTypeNode().Attributes
	case KindImportDeclaration, KindJSImportDeclaration:
		attributes = node.AsImportDeclaration().Attributes
	case KindExportDeclaration:
		attributes = node.AsExportDeclaration().Attributes
	}
	if attributes != nil {
		_, ok := attributes.GetResolutionModeOverride()
		return ok
	}
	return false
}

func IsStringTextContainingNode(node *Node) bool {
	return node.Kind == KindStringLiteral || IsTemplateLiteralKind(node.Kind)
}

func IsTemplateLiteralKind(kind Kind) bool {
	return KindFirstTemplateToken <= kind && kind <= KindLastTemplateToken
}

func IsTemplateLiteralToken(node *Node) bool {
	return IsTemplateLiteralKind(node.Kind)
}

func GetExternalModuleImportEqualsDeclarationExpression(node *Node) *Node {
	debug.Assert(IsExternalModuleImportEqualsDeclaration(node))
	return node.AsImportEqualsDeclaration().ModuleReference.Expression()
}

func CreateModifiersFromModifierFlags(flags ModifierFlags, createModifier func(kind Kind) *Node) []*Node {
	var result []*Node
	if flags&ModifierFlagsExport != 0 {
		result = append(result, createModifier(KindExportKeyword))
	}
	if flags&ModifierFlagsAmbient != 0 {
		result = append(result, createModifier(KindDeclareKeyword))
	}
	if flags&ModifierFlagsDefault != 0 {
		result = append(result, createModifier(KindDefaultKeyword))
	}
	if flags&ModifierFlagsConst != 0 {
		result = append(result, createModifier(KindConstKeyword))
	}
	if flags&ModifierFlagsPublic != 0 {
		result = append(result, createModifier(KindPublicKeyword))
	}
	if flags&ModifierFlagsPrivate != 0 {
		result = append(result, createModifier(KindPrivateKeyword))
	}
	if flags&ModifierFlagsProtected != 0 {
		result = append(result, createModifier(KindProtectedKeyword))
	}
	if flags&ModifierFlagsAbstract != 0 {
		result = append(result, createModifier(KindAbstractKeyword))
	}
	if flags&ModifierFlagsStatic != 0 {
		result = append(result, createModifier(KindStaticKeyword))
	}
	if flags&ModifierFlagsOverride != 0 {
		result = append(result, createModifier(KindOverrideKeyword))
	}
	if flags&ModifierFlagsReadonly != 0 {
		result = append(result, createModifier(KindReadonlyKeyword))
	}
	if flags&ModifierFlagsAccessor != 0 {
		result = append(result, createModifier(KindAccessorKeyword))
	}
	if flags&ModifierFlagsAsync != 0 {
		result = append(result, createModifier(KindAsyncKeyword))
	}
	if flags&ModifierFlagsIn != 0 {
		result = append(result, createModifier(KindInKeyword))
	}
	if flags&ModifierFlagsOut != 0 {
		result = append(result, createModifier(KindOutKeyword))
	}
	return result
}

func GetThisParameter(signature *Node) *Node {
	// callback tags do not currently support this parameters
	if len(signature.Parameters()) != 0 {
		thisParameter := signature.Parameters()[0]
		if IsThisParameter(thisParameter) {
			return thisParameter
		}
	}
	return nil
}

func ReplaceModifiers(factory *NodeFactory, node *Node, modifierArray *ModifierList) *Node {
	switch node.Kind {
	case KindTypeParameter:
		return factory.UpdateTypeParameterDeclaration(
			node.AsTypeParameterDeclaration(),
			modifierArray,
			node.Name(),
			node.AsTypeParameterDeclaration().Constraint,
			node.AsTypeParameterDeclaration().Expression,
			node.AsTypeParameterDeclaration().DefaultType,
		)
	case KindParameter:
		return factory.UpdateParameterDeclaration(
			node.AsParameterDeclaration(),
			modifierArray,
			node.AsParameterDeclaration().DotDotDotToken,
			node.Name(),
			node.QuestionToken(),
			node.Type(),
			node.Initializer(),
		)
	case KindConstructorType:
		return factory.UpdateConstructorTypeNode(
			node.AsConstructorTypeNode(),
			modifierArray,
			node.TypeParameterList(),
			node.ParameterList(),
			node.Type(),
		)
	case KindPropertySignature:
		return factory.UpdatePropertySignatureDeclaration(
			node.AsPropertySignatureDeclaration(),
			modifierArray,
			node.Name(),
			node.PostfixToken(),
			node.Type(),
			node.Initializer(),
		)
	case KindPropertyDeclaration:
		return factory.UpdatePropertyDeclaration(
			node.AsPropertyDeclaration(),
			modifierArray,
			node.Name(),
			node.PostfixToken(),
			node.Type(),
			node.Initializer(),
		)
	case KindMethodSignature:
		return factory.UpdateMethodSignatureDeclaration(
			node.AsMethodSignatureDeclaration(),
			modifierArray,
			node.Name(),
			node.PostfixToken(),
			node.TypeParameterList(),
			node.ParameterList(),
			node.Type(),
		)
	case KindMethodDeclaration:
		return factory.UpdateMethodDeclaration(
			node.AsMethodDeclaration(),
			modifierArray,
			node.AsMethodDeclaration().AsteriskToken,
			node.Name(),
			node.PostfixToken(),
			node.TypeParameterList(),
			node.ParameterList(),
			node.Type(),
			node.AsMethodDeclaration().FullSignature,
			node.Body(),
		)
	case KindConstructor:
		return factory.UpdateConstructorDeclaration(
			node.AsConstructorDeclaration(),
			modifierArray,
			node.TypeParameterList(),
			node.ParameterList(),
			node.Type(),
			node.AsConstructorDeclaration().FullSignature,
			node.Body(),
		)
	case KindGetAccessor:
		return factory.UpdateGetAccessorDeclaration(
			node.AsGetAccessorDeclaration(),
			modifierArray,
			node.Name(),
			node.TypeParameterList(),
			node.ParameterList(),
			node.Type(),
			node.AsGetAccessorDeclaration().FullSignature,
			node.Body(),
		)
	case KindSetAccessor:
		return factory.UpdateSetAccessorDeclaration(
			node.AsSetAccessorDeclaration(),
			modifierArray,
			node.Name(),
			node.TypeParameterList(),
			node.ParameterList(),
			node.Type(),
			node.AsSetAccessorDeclaration().FullSignature,
			node.Body(),
		)
	case KindIndexSignature:
		return factory.UpdateIndexSignatureDeclaration(
			node.AsIndexSignatureDeclaration(),
			modifierArray,
			node.ParameterList(),
			node.Type(),
		)
	case KindFunctionExpression:
		return factory.UpdateFunctionExpression(
			node.AsFunctionExpression(),
			modifierArray,
			node.AsFunctionExpression().AsteriskToken,
			node.Name(),
			node.TypeParameterList(),
			node.ParameterList(),
			node.Type(),
			node.AsFunctionExpression().FullSignature,
			node.Body(),
		)
	case KindArrowFunction:
		return factory.UpdateArrowFunction(
			node.AsArrowFunction(),
			modifierArray,
			node.TypeParameterList(),
			node.ParameterList(),
			node.Type(),
			node.AsArrowFunction().FullSignature,
			node.AsArrowFunction().EqualsGreaterThanToken,
			node.Body(),
		)
	case KindClassExpression:
		return factory.UpdateClassExpression(
			node.AsClassExpression(),
			modifierArray,
			node.Name(),
			node.TypeParameterList(),
			node.AsClassExpression().HeritageClauses,
			node.MemberList(),
		)
	case KindVariableStatement:
		return factory.UpdateVariableStatement(
			node.AsVariableStatement(),
			modifierArray,
			node.AsVariableStatement().DeclarationList,
		)
	case KindFunctionDeclaration:
		return factory.UpdateFunctionDeclaration(
			node.AsFunctionDeclaration(),
			modifierArray,
			node.AsFunctionDeclaration().AsteriskToken,
			node.Name(),
			node.TypeParameterList(),
			node.ParameterList(),
			node.Type(),
			node.AsFunctionDeclaration().FullSignature,
			node.Body(),
		)
	case KindClassDeclaration:
		return factory.UpdateClassDeclaration(
			node.AsClassDeclaration(),
			modifierArray,
			node.Name(),
			node.TypeParameterList(),
			node.AsClassDeclaration().HeritageClauses,
			node.MemberList(),
		)
	case KindInterfaceDeclaration:
		return factory.UpdateInterfaceDeclaration(
			node.AsInterfaceDeclaration(),
			modifierArray,
			node.Name(),
			node.TypeParameterList(),
			node.AsInterfaceDeclaration().HeritageClauses,
			node.MemberList(),
		)
	case KindTypeAliasDeclaration:
		return factory.UpdateTypeAliasDeclaration(
			node.AsTypeAliasDeclaration(),
			modifierArray,
			node.Name(),
			node.TypeParameterList(),
			node.Type(),
		)
	case KindEnumDeclaration:
		return factory.UpdateEnumDeclaration(
			node.AsEnumDeclaration(),
			modifierArray,
			node.Name(),
			node.MemberList(),
		)
	case KindModuleDeclaration:
		return factory.UpdateModuleDeclaration(
			node.AsModuleDeclaration(),
			modifierArray,
			node.AsModuleDeclaration().Keyword,
			node.Name(),
			node.Body(),
		)
	case KindImportEqualsDeclaration:
		return factory.UpdateImportEqualsDeclaration(
			node.AsImportEqualsDeclaration(),
			modifierArray,
			node.IsTypeOnly(),
			node.Name(),
			node.AsImportEqualsDeclaration().ModuleReference,
		)
	case KindImportDeclaration:
		return factory.UpdateImportDeclaration(
			node.AsImportDeclaration(),
			modifierArray,
			node.ImportClause(),
			node.ModuleSpecifier(),
			node.AsImportDeclaration().Attributes,
		)
	case KindExportAssignment:
		return factory.UpdateExportAssignment(
			node.AsExportAssignment(),
			modifierArray,
			node.AsExportAssignment().IsExportEquals,
			node.Type(),
			node.Expression(),
		)
	case KindExportDeclaration:
		return factory.UpdateExportDeclaration(
			node.AsExportDeclaration(),
			modifierArray,
			node.IsTypeOnly(),
			node.AsExportDeclaration().ExportClause,
			node.ModuleSpecifier(),
			node.AsExportDeclaration().Attributes,
		)
	}
	panic(fmt.Sprintf("Node that does not have modifiers tried to have modifier replaced: %d", node.Kind))
}

func IsLateVisibilityPaintedStatement(node *Node) bool {
	switch node.Kind {
	case KindImportDeclaration,
		KindJSImportDeclaration,
		KindImportEqualsDeclaration,
		KindVariableStatement,
		KindClassDeclaration,
		KindFunctionDeclaration,
		KindModuleDeclaration,
		KindTypeAliasDeclaration,
		KindJSTypeAliasDeclaration,
		KindInterfaceDeclaration,
		KindEnumDeclaration:
		return true
	default:
		return false
	}
}

func IsExternalModuleAugmentation(node *Node) bool {
	return IsAmbientModule(node) && IsModuleAugmentationExternal(node)
}

func GetSourceFileOfModule(module *Symbol) *SourceFile {
	declaration := module.ValueDeclaration
	if declaration == nil {
		declaration = GetNonAugmentationDeclaration(module)
	}
	return GetSourceFileOfNode(declaration)
}

func GetNonAugmentationDeclaration(symbol *Symbol) *Node {
	return core.Find(symbol.Declarations, func(d *Node) bool {
		return !IsExternalModuleAugmentation(d) && !IsGlobalScopeAugmentation(d)
	})
}

func IsTypeDeclaration(node *Node) bool {
	switch node.Kind {
	case KindTypeParameter, KindClassDeclaration, KindInterfaceDeclaration, KindTypeAliasDeclaration, KindJSTypeAliasDeclaration, KindEnumDeclaration:
		return true
	case KindImportClause:
		return node.IsTypeOnly()
	case KindImportSpecifier, KindExportSpecifier:
		return node.Parent.Parent.IsTypeOnly()
	default:
		return false
	}
}

func IsTypeDeclarationName(name *Node) bool {
	return name.Kind == KindIdentifier &&
		IsTypeDeclaration(name.Parent) &&
		GetNameOfDeclaration(name.Parent) == name
}

func IsRightSideOfPropertyAccess(node *Node) bool {
	return node.Parent.Kind == KindPropertyAccessExpression && node.Parent.Name() == node
}

func IsArgumentExpressionOfElementAccess(node *Node) bool {
	return node.Parent != nil && node.Parent.Kind == KindElementAccessExpression && node.Parent.AsElementAccessExpression().ArgumentExpression == node
}

func ClimbPastPropertyAccess(node *Node) *Node {
	if IsRightSideOfPropertyAccess(node) {
		return node.Parent
	}
	return node
}

func climbPastPropertyOrElementAccess(node *Node) *Node {
	if IsRightSideOfPropertyAccess(node) || IsArgumentExpressionOfElementAccess(node) {
		return node.Parent
	}
	return node
}

func selectExpressionOfCallOrNewExpressionOrDecorator(node *Node) *Node {
	if IsCallExpression(node) || IsNewExpression(node) || IsDecorator(node) {
		return node.Expression()
	}
	return nil
}

func selectTagOfTaggedTemplateExpression(node *Node) *Node {
	if IsTaggedTemplateExpression(node) {
		return node.AsTaggedTemplateExpression().Tag
	}
	return nil
}

func selectTagNameOfJsxOpeningLikeElement(node *Node) *Node {
	if IsJsxOpeningElement(node) || IsJsxSelfClosingElement(node) {
		return node.TagName()
	}
	return nil
}

func IsCallExpressionTarget(node *Node, includeElementAccess bool, skipPastOuterExpressions bool) bool {
	return isCalleeWorker(node, IsCallExpression, selectExpressionOfCallOrNewExpressionOrDecorator, includeElementAccess, skipPastOuterExpressions)
}

func IsNewExpressionTarget(node *Node, includeElementAccess bool, skipPastOuterExpressions bool) bool {
	return isCalleeWorker(node, IsNewExpression, selectExpressionOfCallOrNewExpressionOrDecorator, includeElementAccess, skipPastOuterExpressions)
}

func IsCallOrNewExpressionTarget(node *Node, includeElementAccess bool, skipPastOuterExpressions bool) bool {
	return isCalleeWorker(node, IsCallOrNewExpression, selectExpressionOfCallOrNewExpressionOrDecorator, includeElementAccess, skipPastOuterExpressions)
}

func IsTaggedTemplateTag(node *Node, includeElementAccess bool, skipPastOuterExpressions bool) bool {
	return isCalleeWorker(node, IsTaggedTemplateExpression, selectTagOfTaggedTemplateExpression, includeElementAccess, skipPastOuterExpressions)
}

func IsDecoratorTarget(node *Node, includeElementAccess bool, skipPastOuterExpressions bool) bool {
	return isCalleeWorker(node, IsDecorator, selectExpressionOfCallOrNewExpressionOrDecorator, includeElementAccess, skipPastOuterExpressions)
}

func IsJsxOpeningLikeElementTagName(node *Node, includeElementAccess bool, skipPastOuterExpressions bool) bool {
	return isCalleeWorker(node, IsJsxOpeningLikeElement, selectTagNameOfJsxOpeningLikeElement, includeElementAccess, skipPastOuterExpressions)
}

func isCalleeWorker(
	node *Node,
	pred func(*Node) bool,
	calleeSelector func(*Node) *Node,
	includeElementAccess bool,
	skipPastOuterExpressions bool,
) bool {
	var target *Node
	if includeElementAccess {
		target = climbPastPropertyOrElementAccess(node)
	} else {
		target = ClimbPastPropertyAccess(node)
	}
	if skipPastOuterExpressions {
		// Only skip outer expressions if the target is actually an expression node
		if IsExpression(target) {
			target = SkipOuterExpressions(target, OEKAll)
		}
	}
	return target != nil && target.Parent != nil && pred(target.Parent) && calleeSelector(target.Parent) == target
}

func IsRightSideOfQualifiedNameOrPropertyAccess(node *Node) bool {
	parent := node.Parent
	switch parent.Kind {
	case KindQualifiedName:
		return parent.AsQualifiedName().Right == node
	case KindPropertyAccessExpression:
		return parent.AsPropertyAccessExpression().Name() == node
	case KindMetaProperty:
		return parent.AsMetaProperty().Name() == node
	}
	return false
}

func ShouldTransformImportCall(fileName string, options *core.CompilerOptions, impliedNodeFormatForEmit core.ModuleKind) bool {
	moduleKind := options.GetEmitModuleKind()
	if core.ModuleKindNode16 <= moduleKind && moduleKind <= core.ModuleKindNodeNext || moduleKind == core.ModuleKindPreserve {
		return false
	}
	return impliedNodeFormatForEmit < core.ModuleKindES2015
}

func HasQuestionToken(node *Node) bool {
	return IsQuestionToken(node.QuestionToken())
}

func IsJsxOpeningLikeElement(node *Node) bool {
	return IsJsxOpeningElement(node) || IsJsxSelfClosingElement(node)
}

func GetInvokedExpression(node *Node) *Node {
	switch node.Kind {
	case KindTaggedTemplateExpression:
		return node.AsTaggedTemplateExpression().Tag
	case KindJsxOpeningElement, KindJsxSelfClosingElement:
		return node.TagName()
	case KindBinaryExpression:
		return node.AsBinaryExpression().Right
	case KindJsxOpeningFragment:
		return node
	default:
		return node.Expression()
	}
}

func IsCallOrNewExpression(node *Node) bool {
	return IsCallExpression(node) || IsNewExpression(node)
}

func IndexOfNode(nodes []*Node, node *Node) int {
	index, ok := slices.BinarySearchFunc(nodes, node, CompareNodePositions)
	if ok {
		return index
	}
	return -1
}

func CompareNodePositions(n1, n2 *Node) int {
	return core.CompareTextRanges(n1.Loc, n2.Loc)
}

func IsUnterminatedLiteral(node *Node) bool {
	return IsLiteralKind(node.Kind) && node.LiteralLikeData().TokenFlags&TokenFlagsUnterminated != 0 ||
		IsTemplateLiteralKind(node.Kind) && node.TemplateLiteralLikeData().TemplateFlags&TokenFlagsUnterminated != 0
}

// Gets a value indicating whether a class element is either a static or an instance property declaration with an initializer.
func IsInitializedProperty(member *ClassElement) bool {
	return member.Kind == KindPropertyDeclaration &&
		member.Initializer() != nil
}

func IsTrivia(token Kind) bool {
	return KindFirstTriviaToken <= token && token <= KindLastTriviaToken
}

func HasDecorators(node *Node) bool {
	return HasSyntacticModifier(node, ModifierFlagsDecorator)
}

type hasFileNameImpl struct {
	fileName string
	path     tspath.Path
}

func NewHasFileName(fileName string, path tspath.Path) HasFileName {
	return &hasFileNameImpl{
		fileName: fileName,
		path:     path,
	}
}

func (h *hasFileNameImpl) FileName() string {
	return h.fileName
}

func (h *hasFileNameImpl) Path() tspath.Path {
	return h.path
}

func GetSemanticJsxChildren(children []*JsxChild) []*JsxChild {
	return core.Filter(children, func(i *JsxChild) bool {
		switch i.Kind {
		case KindJsxExpression:
			return i.Expression() != nil
		case KindJsxText:
			return !i.AsJsxText().ContainsOnlyTriviaWhiteSpaces
		default:
			return true
		}
	})
}

// Returns true if the node kind has a comment property.
func hasComment(kind Kind) bool {
	switch kind {
	case KindJSDoc, KindJSDocUnknownTag, KindJSDocAugmentsTag, KindJSDocImplementsTag,
		KindJSDocDeprecatedTag, KindJSDocPublicTag, KindJSDocPrivateTag, KindJSDocProtectedTag,
		KindJSDocReadonlyTag, KindJSDocOverrideTag, KindJSDocCallbackTag, KindJSDocOverloadTag,
		KindJSDocParameterTag, KindJSDocPropertyTag, KindJSDocReturnTag, KindJSDocThisTag,
		KindJSDocTypeTag, KindJSDocTemplateTag, KindJSDocTypedefTag, KindJSDocSeeTag,
		KindJSDocThrowsTag, KindJSDocSatisfiesTag, KindJSDocImportTag:
		return true
	default:
		return false
	}
}

func IsAssignmentPattern(node *Node) bool {
	return node.Kind == KindArrayLiteralExpression || node.Kind == KindObjectLiteralExpression
}

func GetElementsOfBindingOrAssignmentPattern(name *Node) []*Node {
	switch name.Kind {
	case KindObjectBindingPattern, KindArrayBindingPattern, KindArrayLiteralExpression:
		// `a` in `{a}`
		// `a` in `[a]`
		return name.Elements()
	case KindObjectLiteralExpression:
		// `a` in `{a}`
		return name.Properties()
	}
	return nil
}

func IsDeclarationBindingElement(bindingElement *Node) bool {
	switch bindingElement.Kind {
	case KindVariableDeclaration, KindParameter, KindBindingElement:
		return true
	default:
		return false
	}
}

/**
 * Gets the name of an BindingOrAssignmentElement.
 */
func GetTargetOfBindingOrAssignmentElement(bindingElement *Node) *Node {
	if IsDeclarationBindingElement(bindingElement) {
		// `a` in `let { a } = ...`
		// `a` in `let { a = 1 } = ...`
		// `b` in `let { a: b } = ...`
		// `b` in `let { a: b = 1 } = ...`
		// `a` in `let { ...a } = ...`
		// `{b}` in `let { a: {b} } = ...`
		// `{b}` in `let { a: {b} = 1 } = ...`
		// `[b]` in `let { a: [b] } = ...`
		// `[b]` in `let { a: [b] = 1 } = ...`
		// `a` in `let [a] = ...`
		// `a` in `let [a = 1] = ...`
		// `a` in `let [...a] = ...`
		// `{a}` in `let [{a}] = ...`
		// `{a}` in `let [{a} = 1] = ...`
		// `[a]` in `let [[a]] = ...`
		// `[a]` in `let [[a] = 1] = ...`
		return bindingElement.Name()
	}

	if IsObjectLiteralElement(bindingElement) {
		switch bindingElement.Kind {
		case KindPropertyAssignment:
			// `b` in `({ a: b } = ...)`
			// `b` in `({ a: b = 1 } = ...)`
			// `{b}` in `({ a: {b} } = ...)`
			// `{b}` in `({ a: {b} = 1 } = ...)`
			// `[b]` in `({ a: [b] } = ...)`
			// `[b]` in `({ a: [b] = 1 } = ...)`
			// `b.c` in `({ a: b.c } = ...)`
			// `b.c` in `({ a: b.c = 1 } = ...)`
			// `b[0]` in `({ a: b[0] } = ...)`
			// `b[0]` in `({ a: b[0] = 1 } = ...)`
			return GetTargetOfBindingOrAssignmentElement(bindingElement.Initializer())
		case KindShorthandPropertyAssignment:
			// `a` in `({ a } = ...)`
			// `a` in `({ a = 1 } = ...)`
			return bindingElement.Name()
		case KindSpreadAssignment:
			// `a` in `({ ...a } = ...)`
			return GetTargetOfBindingOrAssignmentElement(bindingElement.Expression())
		}

		// no target
		return nil
	}

	if IsAssignmentExpression(bindingElement /*excludeCompoundAssignment*/, true) {
		// `a` in `[a = 1] = ...`
		// `{a}` in `[{a} = 1] = ...`
		// `[a]` in `[[a] = 1] = ...`
		// `a.b` in `[a.b = 1] = ...`
		// `a[0]` in `[a[0] = 1] = ...`
		return GetTargetOfBindingOrAssignmentElement(bindingElement.AsBinaryExpression().Left)
	}

	if IsSpreadElement(bindingElement) {
		// `a` in `[...a] = ...`
		return GetTargetOfBindingOrAssignmentElement(bindingElement.Expression())
	}

	// `a` in `[a] = ...`
	// `{a}` in `[{a}] = ...`
	// `[a]` in `[[a]] = ...`
	// `a.b` in `[a.b] = ...`
	// `a[0]` in `[a[0]] = ...`
	return bindingElement
}

func TryGetPropertyNameOfBindingOrAssignmentElement(bindingElement *Node) *Node {
	switch bindingElement.Kind {
	case KindBindingElement:
		// `a` in `let { a: b } = ...`
		// `[a]` in `let { [a]: b } = ...`
		// `"a"` in `let { "a": b } = ...`
		// `1` in `let { 1: b } = ...`
		if bindingElement.PropertyName() != nil {
			propertyName := bindingElement.PropertyName()
			// if IsPrivateIdentifier(propertyName) {
			// 	return Debug.failBadSyntaxKind(propertyName) // !!!
			// }
			if IsComputedPropertyName(propertyName) && IsStringOrNumericLiteralLike(propertyName.Expression()) {
				return propertyName.Expression()
			}
			return propertyName
		}
	case KindPropertyAssignment:
		// `a` in `({ a: b } = ...)`
		// `[a]` in `({ [a]: b } = ...)`
		// `"a"` in `({ "a": b } = ...)`
		// `1` in `({ 1: b } = ...)`
		if bindingElement.Name() != nil {
			propertyName := bindingElement.Name()
			// if IsPrivateIdentifier(propertyName) {
			// 	return Debug.failBadSyntaxKind(propertyName) // !!!
			// }
			if IsComputedPropertyName(propertyName) && IsStringOrNumericLiteralLike(propertyName.Expression()) {
				return propertyName.Expression()
			}
			return propertyName
		}
	case KindSpreadAssignment:
		// `a` in `({ ...a } = ...)`
		// if IsPrivateIdentifier(bindingElement.Name()) {
		// 	return Debug.failBadSyntaxKind(bindingElement.Name()) // !!!
		// }
		return bindingElement.Name()
	}

	target := GetTargetOfBindingOrAssignmentElement(bindingElement)
	if target != nil && IsPropertyName(target) {
		return target
	}
	return nil
}

/**
 * Walk an AssignmentPattern to determine if it contains object rest (`...`) syntax. We cannot rely on
 * propagation of `TransformFlags.ContainsObjectRestOrSpread` since it isn't propagated by default in
 * ObjectLiteralExpression and ArrayLiteralExpression since we do not know whether they belong to an
 * AssignmentPattern at the time the nodes are parsed.
 */
func ContainsObjectRestOrSpread(node *Node) bool {
	if node.SubtreeFacts()&SubtreeContainsObjectRestOrSpread != 0 {
		return true
	}
	if node.SubtreeFacts()&SubtreeContainsESObjectRestOrSpread != 0 {
		// check for nested spread assignments, otherwise '{ x: { a, ...b } = foo } = c'
		// will not be correctly interpreted by the rest/spread transformer
		for _, element := range GetElementsOfBindingOrAssignmentPattern(node) {
			target := GetTargetOfBindingOrAssignmentElement(element)
			if target != nil && IsAssignmentPattern(target) {
				if target.SubtreeFacts()&SubtreeContainsObjectRestOrSpread != 0 {
					return true
				}
				if target.SubtreeFacts()&SubtreeContainsESObjectRestOrSpread != 0 {
					if ContainsObjectRestOrSpread(target) {
						return true
					}
				}
			}
		}
	}
	return false
}

func IsEmptyObjectLiteral(expression *Node) bool {
	return IsObjectLiteralExpression(expression) && len(expression.Properties()) == 0
}

func IsEmptyArrayLiteral(expression *Node) bool {
	return IsArrayLiteralExpression(expression) && len(expression.Elements()) == 0
}

func GetRestIndicatorOfBindingOrAssignmentElement(bindingElement *Node) *Node {
	switch bindingElement.Kind {
	case KindParameter:
		return bindingElement.AsParameterDeclaration().DotDotDotToken
	case KindBindingElement:
		return bindingElement.AsBindingElement().DotDotDotToken
	case KindSpreadElement, KindSpreadAssignment:
		return bindingElement
	}
	return nil
}

func IsJSDocNameReferenceContext(node *Node) bool {
	return node.Flags&NodeFlagsJSDoc != 0 && FindAncestor(node, func(node *Node) bool {
		return IsJSDocNameReference(node) || IsJSDocLinkLike(node)
	}) != nil
}

// GetJSDocRoot returns the containing JSDoc node for a node inside a JSDoc comment.
func GetJSDocRoot(node *Node) *Node {
	return FindAncestor(node.Parent, func(n *Node) bool {
		return n.Kind == KindJSDoc
	})
}

// GetJSDocHost returns the declaration that the JSDoc comment containing the given node is attached to.
func GetJSDocHost(node *Node) *Node {
	jsDoc := GetJSDocRoot(node)
	if jsDoc == nil {
		return nil
	}
	return jsDoc.Parent
}

// GetHostSignatureFromJSDoc returns the function-like declaration that hosts the JSDoc comment
// containing the given node. This is used to resolve @link references to parameters.
func GetHostSignatureFromJSDoc(node *Node) *Node {
	host := GetJSDocHost(node)
	if host == nil {
		return nil
	}
	// !!! Strada's getEffectiveJSDocHost applies JS assignment pattern transforms (getSourceOfAssignment, getSourceOfDefaultedAssignment, etc.) not yet ported
	if IsPropertySignatureDeclaration(host) && host.Type() != nil && IsFunctionLike(host.Type()) {
		return host.Type()
	}
	if IsFunctionLike(host) {
		return host
	}
	return nil
}

// Finds the declaration that owns the JSDoc for a function-like node.
// Keep these hosts aligned with JSDoc parameter reparsing so unmatched @param diagnostics use the same attachment rules.
// Keep in sync with getNextJSDocCommentLocation in the API's src/ast/jsdoc.ts
func GetNextJSDocCommentLocation(node *Node) *Node {
	if parent := node.Parent; parent != nil {
		switch parent.Kind {
		case KindPropertyAssignment, KindExportAssignment, KindPropertyDeclaration, KindVariableDeclaration,
			KindSatisfiesExpression, KindReturnStatement, KindVariableStatement, KindExpressionStatement:
			return parent
		case KindVariableDeclarationList:
			if parent.AsVariableDeclarationList().Declarations.Nodes[0] == node {
				return parent
			}
		}
	}
	return nil
}

func IsImportOrImportEqualsDeclaration(node *Node) bool {
	return IsImportDeclaration(node) || IsImportEqualsDeclaration(node)
}

func IsPrimitiveLiteralValue(node *Node, includeBigInt bool) bool {
	switch node.Kind {
	case KindTrueKeyword,
		KindFalseKeyword,
		KindNumericLiteral,
		KindStringLiteral,
		KindNoSubstitutionTemplateLiteral:
		return true
	case KindBigIntLiteral:
		return includeBigInt
	case KindPrefixUnaryExpression:
		if node.AsPrefixUnaryExpression().Operator == KindMinusToken {
			return IsNumericLiteral(node.AsPrefixUnaryExpression().Operand) || (includeBigInt && IsBigIntLiteral(node.AsPrefixUnaryExpression().Operand))
		}
		if node.AsPrefixUnaryExpression().Operator == KindPlusToken {
			return IsNumericLiteral(node.AsPrefixUnaryExpression().Operand)
		}
		return false
	default:
		return false
	}
}

func HasInferredType(node *Node) bool {
	// Debug.type<HasInferredType>(node); // !!!
	switch node.Kind {
	case KindParameter,
		KindPropertySignature,
		KindPropertyDeclaration,
		KindBindingElement,
		KindPropertyAccessExpression,
		KindElementAccessExpression,
		KindBinaryExpression,
		KindCallExpression,
		KindVariableDeclaration,
		KindExportAssignment,
		KindPropertyAssignment,
		KindShorthandPropertyAssignment,
		KindJSDocParameterTag,
		KindJSDocPropertyTag:
		return true
	default:
		// assertType<never>(node); // !!!
		return false
	}
}

func IsKeyword(token Kind) bool {
	return KindFirstKeyword <= token && token <= KindLastKeyword
}

func IsNonContextualKeyword(token Kind) bool {
	return IsKeyword(token) && !IsContextualKeyword(token)
}

func HasModifier(node *Node, flags ModifierFlags) bool {
	return node.ModifierFlags()&flags != 0
}

func IsExpandoInitializer(declaration *Node, initializer *Node) bool {
	if initializer == nil {
		return false
	}
	if IsFunctionExpressionOrArrowFunction(initializer) {
		return true
	}
	if IsInJSFile(initializer) {
		return IsClassExpression(initializer) || (IsObjectLiteralExpression(initializer) && len(initializer.Properties()) == 0 && declaration.Type() == nil)
	}
	return false
}

func GetContainingFunction(node *Node) *Node {
	return FindAncestor(node.Parent, IsFunctionLike)
}

func ImportFromModuleSpecifier(node *Node) *Node {
	if result := TryGetImportFromModuleSpecifier(node); result != nil {
		return result
	}
	debug.FailBadSyntaxKind(node.Parent)
	return nil
}

func TryGetImportFromModuleSpecifier(node *StringLiteralLike) *Node {
	switch node.Parent.Kind {
	case KindImportDeclaration, KindJSImportDeclaration, KindExportDeclaration:
		return node.Parent
	case KindExternalModuleReference:
		return node.Parent.Parent
	case KindCallExpression:
		if IsImportCall(node.Parent) || IsRequireCall(node.Parent, false /*requireStringLiteralLikeArgument*/) {
			return node.Parent
		}
		return nil
	case KindLiteralType:
		if !IsStringLiteral(node) {
			return nil
		}
		if IsImportTypeNode(node.Parent.Parent) {
			return node.Parent.Parent
		}
		return nil
	}
	return nil
}

func IsImplicitlyExportedJSDocDeclaration(node *Node) bool {
	if !IsSourceFile(node.Parent) || !IsExternalOrCommonJSModule(node.Parent.AsSourceFile()) {
		return false
	}
	if IsJSTypeAliasDeclaration(node) {
		return true
	}
	// A reparsed ModuleDeclaration synthesized from a JSDoc @typedef/@callback
	// dotted name should also be treated as implicitly exported in modules.
	return IsModuleDeclaration(node) && node.Flags&NodeFlagsReparsed != 0
}

func HasContextSensitiveParameters(node *Node) bool {
	// Functions with type parameters are not context sensitive.
	if node.TypeParameters() == nil {
		// Functions with any parameters that lack type annotations are context sensitive.
		if core.Some(node.Parameters(), func(p *Node) bool { return p.Type() == nil }) {
			return true
		}
		if !IsArrowFunction(node) {
			// If the first parameter is not an explicit 'this' parameter, then the function has
			// an implicit 'this' parameter which is subject to contextual typing.
			parameter := core.FirstOrNil(node.Parameters())
			if parameter == nil || !IsThisParameter(parameter) {
				return node.Flags&NodeFlagsContainsThis != 0
			}
		}
	}
	return false
}

func IsInfinityOrNaNString(name string) bool {
	return name == "Infinity" || name == "-Infinity" || name == "NaN"
}

func GetFirstConstructorWithBody(node *Node) *Node {
	for _, member := range node.Members() {
		if IsConstructorDeclaration(member) && NodeIsPresent(member.Body()) {
			return member
		}
	}
	return nil
}

// Returns true for nodes that are considered executable for the purposes of unreachable code detection.
func IsPotentiallyExecutableNode(node *Node) bool {
	if KindFirstStatement <= node.Kind && node.Kind <= KindLastStatement {
		if IsVariableStatement(node) {
			declarationList := node.AsVariableStatement().DeclarationList
			if GetCombinedNodeFlags(declarationList)&NodeFlagsBlockScoped != 0 {
				return true
			}
			declarations := declarationList.AsVariableDeclarationList().Declarations.Nodes
			return core.Some(declarations, func(d *Node) bool {
				return d.Initializer() != nil
			})
		}
		return true
	}
	return IsClassDeclaration(node) || IsEnumDeclaration(node) || IsModuleDeclaration(node)
}

func HasAbstractModifier(node *Node) bool {
	return HasSyntacticModifier(node, ModifierFlagsAbstract)
}

func HasAmbientModifier(node *Node) bool {
	return HasSyntacticModifier(node, ModifierFlagsAmbient)
}

func NodeCanBeDecorated(useLegacyDecorators bool, node *Node, parent *Node, grandparent *Node) bool {
	// private names cannot be used with decorators yet
	if useLegacyDecorators && node.Name() != nil && IsPrivateIdentifier(node.Name()) {
		return false
	}
	switch node.Kind {
	case KindClassDeclaration:
		// class declarations are valid targets
		return true
	case KindClassExpression:
		// class expressions are valid targets for native decorators
		return !useLegacyDecorators
	case KindPropertyDeclaration:
		// property declarations are valid if their parent is a class declaration.
		return parent != nil && (useLegacyDecorators && IsClassDeclaration(parent) ||
			!useLegacyDecorators && IsClassLike(parent) && !HasAbstractModifier(node) && !HasAmbientModifier(node))
	case KindGetAccessor, KindSetAccessor, KindMethodDeclaration:
		// if this method has a body and its parent is a class declaration, this is a valid target.
		return parent != nil && node.Body() != nil && (useLegacyDecorators && IsClassDeclaration(parent) ||
			!useLegacyDecorators && IsClassLike(parent))
	case KindParameter:
		// TODO(rbuckton): ParameterDeclaration decorator support for ES decorators must wait until it is standardized
		if !useLegacyDecorators {
			return false
		}
		// if the parameter's parent has a body and its grandparent is a class declaration, this is a valid target.
		return parent != nil && parent.Body() != nil &&
			(parent.Kind == KindConstructor || parent.Kind == KindMethodDeclaration || parent.Kind == KindSetAccessor) &&
			GetThisParameter(parent) != node && grandparent != nil && grandparent.Kind == KindClassDeclaration
	}

	return false
}

func ClassOrConstructorParameterIsDecorated(useLegacyDecorators bool, node *Node) bool {
	if NodeIsDecorated(useLegacyDecorators, node, nil, nil) {
		return true
	}
	constructor := GetFirstConstructorWithBody(node)
	return constructor != nil && ChildIsDecorated(useLegacyDecorators, constructor, node)
}

func ClassElementOrClassElementParameterIsDecorated(useLegacyDecorators bool, node *Node, parent *Node) bool {
	var parameters *NodeList
	if IsAccessor(node) {
		decls := GetAllAccessorDeclarations(parent.Members(), node)
		var firstAccessorWithDecorators *Node
		if HasDecorators(decls.FirstAccessor) {
			firstAccessorWithDecorators = decls.FirstAccessor
		} else if decls.SecondAccessor != nil && HasDecorators(decls.SecondAccessor) {
			firstAccessorWithDecorators = decls.SecondAccessor
		}
		if firstAccessorWithDecorators == nil || node != firstAccessorWithDecorators {
			return false
		}
		if decls.SetAccessor != nil {
			parameters = decls.SetAccessor.Parameters
		}
	} else if IsMethodDeclaration(node) {
		parameters = node.ParameterList()
	}
	if NodeIsDecorated(useLegacyDecorators, node, parent, nil) {
		return true
	}
	if parameters != nil && len(parameters.Nodes) > 0 {
		for _, parameter := range parameters.Nodes {
			if IsThisParameter(parameter) {
				continue
			}
			if NodeIsDecorated(useLegacyDecorators, parameter, node, parent) {
				return true
			}
		}
	}
	return false
}

func NodeIsDecorated(useLegacyDecorators bool, node *Node, parent *Node, grandparent *Node) bool {
	return HasDecorators(node) && NodeCanBeDecorated(useLegacyDecorators, node, parent, grandparent)
}

func NodeOrChildIsDecorated(useLegacyDecorators bool, node *Node, parent *Node, grandparent *Node) bool {
	return NodeIsDecorated(useLegacyDecorators, node, parent, grandparent) || ChildIsDecorated(useLegacyDecorators, node, parent)
}

func ChildIsDecorated(useLegacyDecorators bool, node *Node, parent *Node) bool {
	switch node.Kind {
	case KindClassDeclaration, KindClassExpression:
		return core.Some(node.Members(), func(m *Node) bool {
			return NodeOrChildIsDecorated(useLegacyDecorators, m, node, parent)
		})
	case KindMethodDeclaration,
		KindSetAccessor,
		KindConstructor:
		return core.Some(node.Parameters(), func(p *Node) bool {
			return NodeIsDecorated(useLegacyDecorators, p, node, parent)
		})
	default:
		return false
	}
}

type AllAccessorDeclarations struct {
	FirstAccessor  *AccessorDeclaration
	SecondAccessor *AccessorDeclaration
	SetAccessor    *SetAccessorDeclaration
	GetAccessor    *GetAccessorDeclaration
}

func GetAllAccessorDeclarationsForDeclaration(accessor *AccessorDeclaration, declarationsOfSymbol []*Node) AllAccessorDeclarations {
	var otherKind Kind
	if accessor.Kind == KindSetAccessor {
		otherKind = KindGetAccessor
	} else if accessor.Kind == KindGetAccessor {
		otherKind = KindSetAccessor
	} else {
		panic(fmt.Sprintf("Unexpected node kind %q", accessor.Kind))
	}
	// otherAccessor := GetDeclarationOfKind(c.getSymbolOfDeclaration(accessor), otherKind)
	var otherAccessor *AccessorDeclaration
	for _, d := range declarationsOfSymbol {
		if d.Kind == otherKind {
			otherAccessor = d
			break
		}
	}

	var firstAccessor *AccessorDeclaration
	var secondAccessor *AccessorDeclaration
	if otherAccessor != nil && (otherAccessor.Pos() < accessor.Pos()) {
		firstAccessor = otherAccessor
		secondAccessor = accessor
	} else {
		firstAccessor = accessor
		secondAccessor = otherAccessor
	}

	var setAccessor *SetAccessorDeclaration
	var getAccessor *GetAccessorDeclaration
	if accessor.Kind == KindSetAccessor {
		setAccessor = accessor.AsSetAccessorDeclaration()
		if otherAccessor != nil {
			getAccessor = otherAccessor.AsGetAccessorDeclaration()
		}
	} else {
		getAccessor = accessor.AsGetAccessorDeclaration()
		if otherAccessor != nil {
			setAccessor = otherAccessor.AsSetAccessorDeclaration()
		}
	}

	return AllAccessorDeclarations{
		FirstAccessor:  firstAccessor,
		SecondAccessor: secondAccessor,
		SetAccessor:    setAccessor,
		GetAccessor:    getAccessor,
	}
}

func GetAllAccessorDeclarations(parentDeclarations []*Node, accessor *AccessorDeclaration) AllAccessorDeclarations {
	if HasDynamicName(accessor) {
		// dynamic names can only be match up via checker symbol lookup, just return an object with just this accessor
		return GetAllAccessorDeclarationsForDeclaration(accessor, []*Node{accessor})
	}

	accessorName := GetPropertyNameForPropertyNameNode(accessor.Name())
	accessorStatic := IsStatic(accessor)
	var matches []*Node
	for _, member := range parentDeclarations {
		if !IsAccessor(member) || IsStatic(member) != accessorStatic {
			continue
		}
		memberName := GetPropertyNameForPropertyNameNode(member.Name())
		if memberName == accessorName {
			matches = append(matches, member)
		}
	}
	return GetAllAccessorDeclarationsForDeclaration(accessor, matches)
}

func IsAsyncFunction(node *Node) bool {
	switch node.Kind {
	case KindFunctionDeclaration, KindFunctionExpression, KindArrowFunction, KindMethodDeclaration:
		data := node.BodyData()
		return data.Body != nil && data.AsteriskToken == nil && HasSyntacticModifier(node, ModifierFlagsAsync)
	}
	return false
}

/**
 * Gets the most likely element type for a TypeNode. This is not an exhaustive test
 * as it assumes a rest argument can only be an array type (either T[], or Array<T>).
 *
 * @param node The type node.
 *
 * @internal
 */
func GetRestParameterElementType(node *ParameterDeclarationNode) *Node {
	if node == nil {
		return node
	}
	if node.Kind == KindArrayType {
		return node.AsArrayTypeNode().ElementType
	}
	if node.Kind == KindTypeReference && node.AsTypeReferenceNode().TypeArguments != nil {
		return core.FirstOrNil(node.AsTypeReferenceNode().TypeArguments.Nodes)
	}
	return nil
}

func TagNamesAreEquivalent(lhs *Expression, rhs *Expression) bool {
	if lhs.Kind != rhs.Kind {
		return false
	}
	switch lhs.Kind {
	case KindIdentifier:
		return lhs.Text() == rhs.Text()
	case KindThisKeyword:
		return true
	case KindJsxNamespacedName:
		return lhs.AsJsxNamespacedName().Namespace.Text() == rhs.AsJsxNamespacedName().Namespace.Text() &&
			lhs.AsJsxNamespacedName().Name().Text() == rhs.AsJsxNamespacedName().Name().Text()
	case KindPropertyAccessExpression:
		return lhs.AsPropertyAccessExpression().Name().Text() == rhs.AsPropertyAccessExpression().Name().Text() &&
			TagNamesAreEquivalent(lhs.Expression(), rhs.Expression())
	}
	panic("Unhandled case in TagNamesAreEquivalent")
}

func IsTagName(node *Node) bool {
	return node.Parent != nil && IsJSDocTag(node.Parent) && node.Parent.TagName() == node
}

// We want to store any numbers/strings if they were a name that could be
// related to a declaration.  So, if we have 'import x = require("something")'
// then we want 'something' to be in the name table.  Similarly, if we have
// "a['propname']" then we want to store "propname" in the name table.
func literalIsName(node *Node) bool {
	return IsDeclarationName(node) ||
		node.Parent.Kind == KindExternalModuleReference ||
		isArgumentOfElementAccessExpression(node) ||
		IsLiteralComputedPropertyDeclarationName(node)
}

func isArgumentOfElementAccessExpression(node *Node) bool {
	return node != nil && node.Parent != nil &&
		node.Parent.Kind == KindElementAccessExpression &&
		node.Parent.AsElementAccessExpression().ArgumentExpression == node
}

// If the given node is part of a subtree of JSDoc nodes that have been cloned into a reparsed construct,
// return the corresponding reparsed clone in the subtree. Otherwise, just return the node.
func GetReparsedNodeForNode(node *Node) *Node {
	if node != nil && node.Flags&NodeFlagsJSDoc != 0 && node.Flags&NodeFlagsReparsed == 0 {
		if file := GetSourceFileOfNode(node); file != nil && len(file.ReparsedClones) != 0 {
			pos, found := slices.BinarySearchFunc(file.ReparsedClones, node, CompareNodePositions)
			if !found && pos > 0 {
				pos--
			}
			candidate := file.ReparsedClones[pos]
			if node.Loc.ContainedBy(candidate.Loc) {
				if reparsed := findCloneInNode(candidate, node); reparsed != nil {
					return reparsed
				}
			}
		}
	}
	return node
}

func findCloneInNode(node *Node, original *Node) *Node {
	for {
		if node.Kind == original.Kind && node.Loc == original.Loc {
			return node
		}
		foundContainingChild := node.ForEachChild(func(n *Node) bool {
			if original.Loc.ContainedBy(n.Loc) {
				node = n
				return true
			}
			return false
		})
		if !foundContainingChild {
			return nil
		}
	}
}

func IsExpandoPropertyDeclaration(node *Node) bool {
	return node != nil && IsBinaryExpression(node)
}

// IsSuperProperty checks if a node is super.x or super[x].
func IsSuperProperty(node *Node) bool {
	return (IsPropertyAccessExpression(node) || IsElementAccessExpression(node)) &&
		node.Expression().Kind == KindSuperKeyword
}

// Indicates whether a node is a potential source of an assigned name for a class, function, or arrow function.
func IsNamedEvaluationSource(node *Node) bool {
	switch node.Kind {
	case KindPropertyAssignment:
		return !IsProtoSetter(node.AsPropertyAssignment().Name())
	case KindShorthandPropertyAssignment:
		return node.AsShorthandPropertyAssignment().ObjectAssignmentInitializer != nil
	case KindVariableDeclaration:
		return IsIdentifier(node.AsVariableDeclaration().Name()) && node.Initializer() != nil
	case KindParameter:
		return IsIdentifier(node.AsParameterDeclaration().Name()) && node.Initializer() != nil && node.AsParameterDeclaration().DotDotDotToken == nil
	case KindBindingElement:
		return IsIdentifier(node.AsBindingElement().Name()) && node.Initializer() != nil && node.AsBindingElement().DotDotDotToken == nil
	case KindPropertyDeclaration:
		return node.Initializer() != nil
	case KindBinaryExpression:
		switch node.AsBinaryExpression().OperatorToken.Kind {
		case KindEqualsToken, KindAmpersandAmpersandEqualsToken, KindBarBarEqualsToken, KindQuestionQuestionEqualsToken:
			return IsIdentifier(node.AsBinaryExpression().Left)
		}
	case KindExportAssignment:
		return true
	}
	return false
}

// Indicates whether a property name is the special `__proto__` property.
// Per the ECMA-262 spec, this only matters for property assignments whose name is
// the Identifier `__proto__`, or the string literal `"__proto__"`, but not for
// computed property names.
func IsProtoSetter(node *Node) bool {
	return (IsIdentifier(node) || IsStringLiteral(node)) && node.Text() == "__proto__"
}
