diff --git a/internal/checker/checker.go b/internal/checker/checker.go index 17491e0dcc..b4cdf7e3af 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -16,6 +16,7 @@ import ( "github.com/microsoft/typescript-go/internal/collections" "github.com/microsoft/typescript-go/internal/compiler/diagnostics" "github.com/microsoft/typescript-go/internal/core" + "github.com/microsoft/typescript-go/internal/evaluator" "github.com/microsoft/typescript-go/internal/jsnum" "github.com/microsoft/typescript-go/internal/printer" "github.com/microsoft/typescript-go/internal/scanner" @@ -556,10 +557,10 @@ type Checker struct { arrayVariances []VarianceFlags globals ast.SymbolTable globalSymbols []*ast.Symbol - evaluate Evaluator + evaluate evaluator.Evaluator stringLiteralTypes map[string]*Type numberLiteralTypes map[jsnum.Number]*Type - bigintLiteralTypes map[PseudoBigInt]*Type + bigintLiteralTypes map[jsnum.PseudoBigInt]*Type enumLiteralTypes map[EnumLiteralKey]*Type indexedAccessTypes map[string]*Type templateLiteralTypes map[string]*Type @@ -831,10 +832,10 @@ func NewChecker(program Program) *Checker { c.canCollectSymbolAliasAccessibilityData = c.compilerOptions.VerbatimModuleSyntax.IsFalseOrUnknown() c.arrayVariances = []VarianceFlags{VarianceFlagsCovariant} c.globals = make(ast.SymbolTable, countGlobalSymbols(c.files)) - c.evaluate = createEvaluator(c.evaluateEntity) + c.evaluate = evaluator.NewEvaluator(c.evaluateEntity, ast.OEKParentheses) c.stringLiteralTypes = make(map[string]*Type) c.numberLiteralTypes = make(map[jsnum.Number]*Type) - c.bigintLiteralTypes = make(map[PseudoBigInt]*Type) + c.bigintLiteralTypes = make(map[jsnum.PseudoBigInt]*Type) c.enumLiteralTypes = make(map[EnumLiteralKey]*Type) c.indexedAccessTypes = make(map[string]*Type) c.templateLiteralTypes = make(map[string]*Type) @@ -946,7 +947,7 @@ func NewChecker(program Program) *Checker { c.enumNumberIndexInfo = &IndexInfo{keyType: c.numberType, valueType: c.stringType, isReadonly: true} c.emptyStringType = c.getStringLiteralType("") c.zeroType = c.getNumberLiteralType(0) - c.zeroBigIntType = c.getBigIntLiteralType(PseudoBigInt{negative: false, base10Value: "0"}) + c.zeroBigIntType = c.getBigIntLiteralType(jsnum.PseudoBigInt{Negative: false, Base10Value: "0"}) c.typeofType = c.getUnionType(core.Map(slices.Sorted(maps.Keys(typeofNEFacts)), c.getStringLiteralType)) c.flowLoopCache = make(map[FlowLoopKey]*Type) c.flowNodeReachable = make(map[*ast.FlowNode]bool) @@ -3508,7 +3509,7 @@ func (c *Checker) checkTestingKnownTruthyType(condExpr *ast.Node, condType *Type } if t.flags&TypeFlagsEnumLiteral != 0 && ast.IsPropertyAccessExpression(location) && core.OrElse(c.typeNodeLinks.Get(location.Expression()).resolvedSymbol, c.unknownSymbol).Flags&ast.SymbolFlagsEnum != 0 { // EnumLiteral type at condition with known value is always truthy or always falsy, likely an error - c.error(location, diagnostics.This_condition_will_always_return_0, anyToString(t.AsLiteralType().value)) + c.error(location, diagnostics.This_condition_will_always_return_0, evaluator.AnyToString(t.AsLiteralType().value)) return } isPropertyExpressionCast := ast.IsPropertyAccessExpression(location) && isTypeAssertion(location.Expression()) @@ -7071,9 +7072,9 @@ func (c *Checker) checkExpressionWorker(node *ast.Node, checkMode CheckMode) *Ty return c.getFreshTypeOfLiteralType(c.getNumberLiteralType(jsnum.FromString(node.Text()))) case ast.KindBigIntLiteral: c.checkGrammarBigIntLiteral(node.AsBigIntLiteral()) - return c.getFreshTypeOfLiteralType(c.getBigIntLiteralType(PseudoBigInt{ - negative: false, - base10Value: parsePseudoBigInt(node.Text()), + return c.getFreshTypeOfLiteralType(c.getBigIntLiteralType(jsnum.PseudoBigInt{ + Negative: false, + Base10Value: parsePseudoBigInt(node.Text()), })) case ast.KindTrueKeyword: return c.trueType @@ -7393,7 +7394,7 @@ func (c *Checker) checkTemplateExpression(node *ast.Node) *Type { } var evaluated any if !ast.IsTaggedTemplateExpression(node.Parent) { - evaluated = c.evaluate(node, node).value + evaluated = c.evaluate(node, node).Value } if evaluated != nil { return c.getFreshTypeOfLiteralType(c.getStringLiteralType(evaluated.(string))) @@ -10047,9 +10048,9 @@ func (c *Checker) checkPrefixUnaryExpression(node *ast.Node) *Type { } case ast.KindBigIntLiteral: if expr.Operator == ast.KindMinusToken { - return c.getFreshTypeOfLiteralType(c.getBigIntLiteralType(PseudoBigInt{ - negative: true, - base10Value: parsePseudoBigInt(expr.Operand.Text()), + return c.getFreshTypeOfLiteralType(c.getBigIntLiteralType(jsnum.PseudoBigInt{ + Negative: true, + Base10Value: parsePseudoBigInt(expr.Operand.Text()), })) } } @@ -11534,7 +11535,7 @@ func (c *Checker) checkBinaryLikeExpression(left *ast.Node, operatorToken *ast.N ast.KindGreaterThanGreaterThanEqualsToken, ast.KindGreaterThanGreaterThanGreaterThanToken, ast.KindGreaterThanGreaterThanGreaterThanEqualsToken: rhsEval := c.evaluate(right, right) - if numValue, ok := rhsEval.value.(jsnum.Number); ok && numValue.Abs() >= 32 { + if numValue, ok := rhsEval.Value.(jsnum.Number); ok && numValue.Abs() >= 32 { c.errorOrSuggestion(ast.IsEnumMember(ast.WalkUpParenthesizedExpressions(right.Parent.Parent)), errorNode, diagnostics.This_operation_can_be_simplified_This_shift_is_identical_to_0_1_2, scanner.GetTextOfNode(left), scanner.TokenToString(operator), (numValue / 32).Floor()) } } @@ -21860,7 +21861,7 @@ func (c *Checker) getDeclaredTypeOfEnum(symbol *ast.Symbol) *Type { for _, member := range declaration.Members() { if c.hasBindableName(member) { memberSymbol := c.getSymbolOfDeclaration(member) - value := c.getEnumMemberValue(member).value + value := c.getEnumMemberValue(member).Value var memberType *Type if value != nil { memberType = c.getEnumLiteralType(value, symbol, memberSymbol) @@ -21888,7 +21889,7 @@ func (c *Checker) getDeclaredTypeOfEnum(symbol *ast.Symbol) *Type { return links.declaredType } -func (c *Checker) getEnumMemberValue(node *ast.Node) EvaluatorResult { +func (c *Checker) getEnumMemberValue(node *ast.Node) evaluator.Result { c.computeEnumMemberValues(node.Parent) return c.enumMemberLinks.Get(node).value } @@ -21922,7 +21923,7 @@ func (c *Checker) computeEnumMemberValues(node *ast.Node) { for _, member := range node.Members() { result := c.computeEnumMemberValue(member, autoValue, previous) c.enumMemberLinks.Get(member).value = result - if value, isNumber := result.value.(jsnum.Number); isNumber { + if value, isNumber := result.Value.(jsnum.Number); isNumber { autoValue = value + 1 } else { autoValue = jsnum.NaN() @@ -21932,7 +21933,7 @@ func (c *Checker) computeEnumMemberValues(node *ast.Node) { } } -func (c *Checker) computeEnumMemberValue(member *ast.Node, autoValue jsnum.Number, previous *ast.Node) EvaluatorResult { +func (c *Checker) computeEnumMemberValue(member *ast.Node, autoValue jsnum.Number, previous *ast.Node) evaluator.Result { if ast.IsComputedNonLiteralName(member.Name()) { c.error(member.Name(), diagnostics.Computed_property_names_are_not_allowed_in_enums) } else { @@ -21947,7 +21948,7 @@ func (c *Checker) computeEnumMemberValue(member *ast.Node, autoValue jsnum.Numbe // In ambient non-const numeric enum declarations, enum members without initializers are // considered computed members (as opposed to having auto-incremented values). if member.Parent.Flags&ast.NodeFlagsAmbient != 0 && !ast.IsEnumConst(member.Parent) { - return evaluatorResult(nil, false, false, false) + return evaluator.NewResult(nil, false, false, false) } // If the member declaration specifies no value, the member is considered a constant enum member. // If the member is the first member in the enum declaration, it is assigned the value zero. @@ -21955,33 +21956,33 @@ func (c *Checker) computeEnumMemberValue(member *ast.Node, autoValue jsnum.Numbe // occurs if the immediately preceding member is not a constant enum member. if autoValue.IsNaN() { c.error(member.Name(), diagnostics.Enum_member_must_have_initializer) - return evaluatorResult(nil, false, false, false) + return evaluator.NewResult(nil, false, false, false) } if c.compilerOptions.GetIsolatedModules() && previous != nil && previous.AsEnumMember().Initializer != nil { prevValue := c.getEnumMemberValue(previous) - _, prevIsNum := prevValue.value.(jsnum.Number) - if !prevIsNum || prevValue.resolvedOtherFiles { + _, prevIsNum := prevValue.Value.(jsnum.Number) + if !prevIsNum || prevValue.ResolvedOtherFiles { c.error(member.Name(), diagnostics.Enum_member_following_a_non_literal_numeric_member_must_have_an_initializer_when_isolatedModules_is_enabled) } } - return evaluatorResult(autoValue, false, false, false) + return evaluator.NewResult(autoValue, false, false, false) } -func (c *Checker) computeConstantEnumMemberValue(member *ast.Node) EvaluatorResult { +func (c *Checker) computeConstantEnumMemberValue(member *ast.Node) evaluator.Result { isConstEnum := ast.IsEnumConst(member.Parent) initializer := member.Initializer() result := c.evaluate(initializer, member) switch { - case result.value != nil: + case result.Value != nil: if isConstEnum { - if numValue, isNumber := result.value.(jsnum.Number); isNumber && (numValue.IsInf() || numValue.IsNaN()) { + if numValue, isNumber := result.Value.(jsnum.Number); isNumber && (numValue.IsInf() || numValue.IsNaN()) { c.error(initializer, core.IfElse(numValue.IsNaN(), diagnostics.X_const_enum_member_initializer_was_evaluated_to_disallowed_value_NaN, diagnostics.X_const_enum_member_initializer_was_evaluated_to_a_non_finite_value)) } } if c.compilerOptions.GetIsolatedModules() { - if _, isString := result.value.(string); isString && !result.isSyntacticallyString { + if _, isString := result.Value.(string); isString && !result.IsSyntacticallyString { memberName := member.Parent.Name().Text() + "." + member.Name().Text() c.error(initializer, diagnostics.X_0_has_a_string_type_but_must_have_syntactically_recognizable_string_syntax_when_isolatedModules_is_enabled, memberName) } @@ -21996,19 +21997,19 @@ func (c *Checker) computeConstantEnumMemberValue(member *ast.Node) EvaluatorResu return result } -func (c *Checker) evaluateEntity(expr *ast.Node, location *ast.Node) EvaluatorResult { +func (c *Checker) evaluateEntity(expr *ast.Node, location *ast.Node) evaluator.Result { switch expr.Kind { case ast.KindIdentifier, ast.KindPropertyAccessExpression: symbol := c.resolveEntityName(expr, ast.SymbolFlagsValue, true /*ignoreErrors*/, false, nil) if symbol == nil { - return evaluatorResult(nil, false, false, false) + return evaluator.NewResult(nil, false, false, false) } if expr.Kind == ast.KindIdentifier { if isInfinityOrNaNString(expr.Text()) && (symbol == c.getGlobalSymbol(expr.Text(), ast.SymbolFlagsValue, nil /*diagnostic*/)) { // Technically we resolved a global lib file here, but the decision to treat this as numeric // is more predicated on the fact that the single-file resolution *didn't* resolve to a // different meaning of `Infinity` or `NaN`. Transpilers handle this no problem. - return evaluatorResult(jsnum.FromString(expr.Text()), false, false, false) + return evaluator.NewResult(jsnum.FromString(expr.Text()), false, false, false) } } if symbol.Flags&ast.SymbolFlagsEnumMember != 0 { @@ -22023,12 +22024,12 @@ func (c *Checker) evaluateEntity(expr *ast.Node, location *ast.Node) EvaluatorRe (location == nil || declaration != location && c.isBlockScopedNameDeclaredBeforeUse(declaration, location)) { result := c.evaluate(declaration.Initializer(), declaration) if location != nil && ast.GetSourceFileOfNode(location) != ast.GetSourceFileOfNode(declaration) { - return evaluatorResult(result.value, false, true, true) + return evaluator.NewResult(result.Value, false, true, true) } - return evaluatorResult(result.value, result.isSyntacticallyString, result.resolvedOtherFiles, true /*hasExternalReferences*/) + return evaluator.NewResult(result.Value, result.IsSyntacticallyString, result.ResolvedOtherFiles, true /*hasExternalReferences*/) } } - return evaluatorResult(nil, false, false, false) + return evaluator.NewResult(nil, false, false, false) case ast.KindElementAccessExpression: root := expr.Expression() if ast.IsEntityNameExpression(root) && ast.IsStringLiteralLike(expr.AsElementAccessExpression().ArgumentExpression) { @@ -22045,24 +22046,24 @@ func (c *Checker) evaluateEntity(expr *ast.Node, location *ast.Node) EvaluatorRe } } } - return evaluatorResult(nil, false, false, false) + return evaluator.NewResult(nil, false, false, false) } panic("Unhandled case in evaluateEntity") } -func (c *Checker) evaluateEnumMember(expr *ast.Node, symbol *ast.Symbol, location *ast.Node) EvaluatorResult { +func (c *Checker) evaluateEnumMember(expr *ast.Node, symbol *ast.Symbol, location *ast.Node) evaluator.Result { declaration := symbol.ValueDeclaration if declaration == nil || declaration == location { c.error(expr, diagnostics.Property_0_is_used_before_being_assigned, c.symbolToString(symbol)) - return evaluatorResult(nil, false, false, false) + return evaluator.NewResult(nil, false, false, false) } if !c.isBlockScopedNameDeclaredBeforeUse(declaration, location) { c.error(expr, diagnostics.A_member_initializer_in_a_enum_declaration_cannot_reference_members_declared_after_it_including_members_defined_in_other_enums) - return evaluatorResult(jsnum.Number(0), false, false, false) + return evaluator.NewResult(jsnum.Number(0), false, false, false) } value := c.getEnumMemberValue(declaration) if location.Parent != declaration.Parent { - return evaluatorResult(value.value, value.isSyntacticallyString, value.resolvedOtherFiles, true /*hasExternalReferences*/) + return evaluator.NewResult(value.Value, value.IsSyntacticallyString, value.ResolvedOtherFiles, true /*hasExternalReferences*/) } return value } @@ -23275,7 +23276,7 @@ func (c *Checker) getNumberLiteralType(value jsnum.Number) *Type { return t } -func (c *Checker) getBigIntLiteralType(value PseudoBigInt) *Type { +func (c *Checker) getBigIntLiteralType(value jsnum.PseudoBigInt) *Type { t := c.bigintLiteralTypes[value] if t == nil { t = c.newLiteralType(TypeFlagsBigIntLiteral, value, nil) @@ -23292,8 +23293,8 @@ func getNumberLiteralValue(t *Type) jsnum.Number { return t.AsLiteralType().value.(jsnum.Number) } -func getBigIntLiteralValue(t *Type) PseudoBigInt { - return t.AsLiteralType().value.(PseudoBigInt) +func getBigIntLiteralValue(t *Type) jsnum.PseudoBigInt { + return t.AsLiteralType().value.(jsnum.PseudoBigInt) } func getBooleanLiteralValue(t *Type) bool { @@ -26606,7 +26607,7 @@ func (c *Checker) getTemplateLiteralType(texts []string, types []*Type) *Type { func (c *Checker) getTemplateStringForType(t *Type) string { switch { case t.flags&(TypeFlagsStringLiteral|TypeFlagsNumberLiteral|TypeFlagsBooleanLiteral|TypeFlagsBigIntLiteral) != 0: - return anyToString(t.AsLiteralType().value) + return evaluator.AnyToString(t.AsLiteralType().value) case t.flags&TypeFlagsNullable != 0: return t.AsIntrinsicType().intrinsicName } @@ -28454,7 +28455,7 @@ func (c *Checker) getIntersectionTypeFacts(t *Type, callerOnlyNeeds TypeFacts) T } func isZeroBigInt(t *Type) bool { - return t.AsLiteralType().value.(PseudoBigInt).base10Value == "0" + return t.AsLiteralType().value.(jsnum.PseudoBigInt).Base10Value == "0" } func (c *Checker) isFunctionObjectType(t *Type) bool { diff --git a/internal/checker/flow.go b/internal/checker/flow.go index 2c0b570ee7..49a89f8264 100644 --- a/internal/checker/flow.go +++ b/internal/checker/flow.go @@ -10,6 +10,7 @@ import ( "github.com/microsoft/typescript-go/internal/binder" "github.com/microsoft/typescript-go/internal/compiler/diagnostics" "github.com/microsoft/typescript-go/internal/core" + "github.com/microsoft/typescript-go/internal/evaluator" "github.com/microsoft/typescript-go/internal/scanner" ) @@ -1729,7 +1730,7 @@ func tryGetNameFromType(t *Type) (string, bool) { case t.flags&TypeFlagsUniqueESSymbol != 0: return t.AsUniqueESSymbolType().name, true case t.flags&TypeFlagsStringOrNumberLiteral != 0: - return anyToString(t.AsLiteralType().value), true + return evaluator.AnyToString(t.AsLiteralType().value), true } return "", false } @@ -1754,7 +1755,7 @@ func (c *Checker) getDestructuringPropertyName(node *ast.Node) (string, bool) { func (c *Checker) getLiteralPropertyNameText(name *ast.Node) (string, bool) { t := c.getLiteralTypeFromPropertyName(name) if t.flags&(TypeFlagsStringLiteral|TypeFlagsNumberLiteral) != 0 { - return anyToString(t.AsLiteralType().value), true + return evaluator.AnyToString(t.AsLiteralType().value), true } return "", false } diff --git a/internal/checker/inference.go b/internal/checker/inference.go index 0d1cd5945e..1a7f9066bf 100644 --- a/internal/checker/inference.go +++ b/internal/checker/inference.go @@ -552,7 +552,7 @@ func (c *Checker) inferToTemplateLiteralType(n *InferenceState, source *Type, ta case left.flags&TypeFlagsBigInt != 0: return left case right.flags&TypeFlagsBigInt != 0: - return c.getBigIntLiteralType(PseudoBigInt{}) // !!! + return c.getBigIntLiteralType(jsnum.PseudoBigInt{}) // !!! case left.flags&TypeFlagsBigIntLiteral != 0: return left case right.flags&TypeFlagsBigIntLiteral != 0 && pseudoBigIntToString(getBigIntLiteralValue(right)) == str: diff --git a/internal/checker/printer.go b/internal/checker/printer.go index 03f16080fa..17e6e19892 100644 --- a/internal/checker/printer.go +++ b/internal/checker/printer.go @@ -197,7 +197,7 @@ func (p *Printer) printValue(value any) { p.printNumberLiteral(value) case bool: p.printBooleanLiteral(value) - case PseudoBigInt: + case jsnum.PseudoBigInt: p.printBigIntLiteral(value) } } @@ -216,11 +216,8 @@ func (p *Printer) printBooleanLiteral(b bool) { p.print(core.IfElse(b, "true", "false")) } -func (p *Printer) printBigIntLiteral(b PseudoBigInt) { - if b.negative { - p.print("-") - } - p.print(b.base10Value) +func (p *Printer) printBigIntLiteral(b jsnum.PseudoBigInt) { + p.print(b.String()) } func (p *Printer) printUniqueESSymbolType(t *Type) { diff --git a/internal/checker/relater.go b/internal/checker/relater.go index 3e1878edea..87d7b5b558 100644 --- a/internal/checker/relater.go +++ b/internal/checker/relater.go @@ -304,8 +304,8 @@ func (c *Checker) isEnumTypeRelatedTo(source *ast.Symbol, target *ast.Symbol, er c.enumRelation[key] = RelationComparisonResultFailed return false } - sourceValue := c.getEnumMemberValue(ast.GetDeclarationOfKind(sourceProperty, ast.KindEnumMember)).value - targetValue := c.getEnumMemberValue(ast.GetDeclarationOfKind(targetProperty, ast.KindEnumMember)).value + sourceValue := c.getEnumMemberValue(ast.GetDeclarationOfKind(sourceProperty, ast.KindEnumMember)).Value + targetValue := c.getEnumMemberValue(ast.GetDeclarationOfKind(targetProperty, ast.KindEnumMember)).Value if sourceValue != targetValue { // If we have 2 enums with *known* values that differ, they are incompatible. if sourceValue != nil && targetValue != nil { diff --git a/internal/checker/types.go b/internal/checker/types.go index 77be679eab..4ab9f9aee0 100644 --- a/internal/checker/types.go +++ b/internal/checker/types.go @@ -6,6 +6,7 @@ import ( "github.com/microsoft/typescript-go/internal/ast" "github.com/microsoft/typescript-go/internal/collections" "github.com/microsoft/typescript-go/internal/core" + "github.com/microsoft/typescript-go/internal/evaluator" ) //go:generate go tool golang.org/x/tools/cmd/stringer -type=SignatureKind -output=stringer_generated.go @@ -328,7 +329,7 @@ type TypeNodeLinks struct { // Links for enum members type EnumMemberLinks struct { - value EvaluatorResult // Constant value of enum member + value evaluator.Result // Constant value of enum member } // Links for assertion expressions @@ -715,11 +716,6 @@ type LiteralType struct { regularType *Type // Regular version of type } -type PseudoBigInt struct { - negative bool - base10Value string -} - // UniqueESSymbolTypeData type UniqueESSymbolType struct { diff --git a/internal/checker/utilities.go b/internal/checker/utilities.go index 4d939af194..6212fd5580 100644 --- a/internal/checker/utilities.go +++ b/internal/checker/utilities.go @@ -1208,21 +1208,6 @@ func isThisProperty(node *ast.Node) bool { return (ast.IsPropertyAccessExpression(node) || ast.IsElementAccessExpression(node)) && node.Expression().Kind == ast.KindThisKeyword } -func anyToString(v any) string { - // !!! This function should behave identically to the expression `"" + v` in JS - switch v := v.(type) { - case string: - return v - case jsnum.Number: - return v.String() - case bool: - return core.IfElse(v, "true", "false") - case PseudoBigInt: - return "(BigInt)" // !!! - } - panic("Unhandled case in anyToString") -} - func isValidNumberString(s string, roundTripOnly bool) bool { if s == "" { return false @@ -1421,136 +1406,6 @@ func isObjectLiteralElementLike(node *ast.Node) bool { return ast.IsObjectLiteralElement(node) } -type EvaluatorResult struct { - value any - isSyntacticallyString bool - resolvedOtherFiles bool - hasExternalReferences bool -} - -func evaluatorResult(value any, isSyntacticallyString bool, resolvedOtherFiles bool, hasExternalReferences bool) EvaluatorResult { - return EvaluatorResult{value, isSyntacticallyString, resolvedOtherFiles, hasExternalReferences} -} - -type Evaluator func(expr *ast.Node, location *ast.Node) EvaluatorResult - -func createEvaluator(evaluateEntity Evaluator) Evaluator { - var evaluate Evaluator - evaluateTemplateExpression := func(expr *ast.Node, location *ast.Node) EvaluatorResult { - var sb strings.Builder - sb.WriteString(expr.AsTemplateExpression().Head.Text()) - resolvedOtherFiles := false - hasExternalReferences := false - for _, span := range expr.AsTemplateExpression().TemplateSpans.Nodes { - spanResult := evaluate(span.Expression(), location) - if spanResult.value == nil { - return evaluatorResult(nil, true /*isSyntacticallyString*/, false, false) - } - sb.WriteString(anyToString(spanResult.value)) - sb.WriteString(span.AsTemplateSpan().Literal.Text()) - resolvedOtherFiles = resolvedOtherFiles || spanResult.resolvedOtherFiles - hasExternalReferences = hasExternalReferences || spanResult.hasExternalReferences - } - return evaluatorResult(sb.String(), true, resolvedOtherFiles, hasExternalReferences) - } - evaluate = func(expr *ast.Node, location *ast.Node) EvaluatorResult { - isSyntacticallyString := false - resolvedOtherFiles := false - hasExternalReferences := false - // It's unclear when/whether we should consider skipping other kinds of outer expressions. - // Type assertions intentionally break evaluation when evaluating literal types, such as: - // type T = `one ${"two" as any} three`; // string - // But it's less clear whether such an assertion should break enum member evaluation: - // enum E { - // A = "one" as any - // } - // SatisfiesExpressions and non-null assertions seem to have even less reason to break - // emitting enum members as literals. However, these expressions also break Babel's - // evaluation (but not esbuild's), and the isolatedModules errors we give depend on - // our evaluation results, so we're currently being conservative so as to issue errors - // on code that might break Babel. - expr = ast.SkipParentheses(expr) - switch expr.Kind { - case ast.KindPrefixUnaryExpression: - result := evaluate(expr.AsPrefixUnaryExpression().Operand, location) - resolvedOtherFiles = result.resolvedOtherFiles - hasExternalReferences = result.hasExternalReferences - if value, ok := result.value.(jsnum.Number); ok { - switch expr.AsPrefixUnaryExpression().Operator { - case ast.KindPlusToken: - return evaluatorResult(value, isSyntacticallyString, resolvedOtherFiles, hasExternalReferences) - case ast.KindMinusToken: - return evaluatorResult(-value, isSyntacticallyString, resolvedOtherFiles, hasExternalReferences) - case ast.KindTildeToken: - return evaluatorResult(value.BitwiseNOT(), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences) - } - } - case ast.KindBinaryExpression: - left := evaluate(expr.AsBinaryExpression().Left, location) - right := evaluate(expr.AsBinaryExpression().Right, location) - operator := expr.AsBinaryExpression().OperatorToken.Kind - isSyntacticallyString = (left.isSyntacticallyString || right.isSyntacticallyString) && expr.AsBinaryExpression().OperatorToken.Kind == ast.KindPlusToken - resolvedOtherFiles = left.resolvedOtherFiles || right.resolvedOtherFiles - hasExternalReferences = left.hasExternalReferences || right.hasExternalReferences - leftNum, leftIsNum := left.value.(jsnum.Number) - rightNum, rightIsNum := right.value.(jsnum.Number) - if leftIsNum && rightIsNum { - switch operator { - case ast.KindBarToken: - return evaluatorResult(leftNum.BitwiseOR(rightNum), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences) - case ast.KindAmpersandToken: - return evaluatorResult(leftNum.BitwiseAND(rightNum), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences) - case ast.KindGreaterThanGreaterThanToken: - return evaluatorResult(leftNum.SignedRightShift(rightNum), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences) - case ast.KindGreaterThanGreaterThanGreaterThanToken: - return evaluatorResult(leftNum.UnsignedRightShift(rightNum), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences) - case ast.KindLessThanLessThanToken: - return evaluatorResult(leftNum.LeftShift(rightNum), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences) - case ast.KindCaretToken: - return evaluatorResult(leftNum.BitwiseXOR(rightNum), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences) - case ast.KindAsteriskToken: - return evaluatorResult(leftNum*rightNum, isSyntacticallyString, resolvedOtherFiles, hasExternalReferences) - case ast.KindSlashToken: - return evaluatorResult(leftNum/rightNum, isSyntacticallyString, resolvedOtherFiles, hasExternalReferences) - case ast.KindPlusToken: - return evaluatorResult(leftNum+rightNum, isSyntacticallyString, resolvedOtherFiles, hasExternalReferences) - case ast.KindMinusToken: - return evaluatorResult(leftNum-rightNum, isSyntacticallyString, resolvedOtherFiles, hasExternalReferences) - case ast.KindPercentToken: - return evaluatorResult(leftNum.Remainder(rightNum), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences) - case ast.KindAsteriskAsteriskToken: - return evaluatorResult(leftNum.Exponentiate(rightNum), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences) - } - } - leftStr, leftIsStr := left.value.(string) - rightStr, rightIsStr := right.value.(string) - if (leftIsStr || leftIsNum) && (rightIsStr || rightIsNum) && operator == ast.KindPlusToken { - if leftIsNum { - leftStr = leftNum.String() - } - if rightIsNum { - rightStr = rightNum.String() - } - return evaluatorResult(leftStr+rightStr, isSyntacticallyString, resolvedOtherFiles, hasExternalReferences) - } - case ast.KindStringLiteral, ast.KindNoSubstitutionTemplateLiteral: - return evaluatorResult(expr.Text(), true /*isSyntacticallyString*/, false, false) - case ast.KindTemplateExpression: - return evaluateTemplateExpression(expr, location) - case ast.KindNumericLiteral: - return evaluatorResult(jsnum.FromString(expr.Text()), false, false, false) - case ast.KindIdentifier, ast.KindElementAccessExpression: - return evaluateEntity(expr, location) - case ast.KindPropertyAccessExpression: - if ast.IsEntityNameExpression(expr) { - return evaluateEntity(expr, location) - } - } - return evaluatorResult(nil, isSyntacticallyString, resolvedOtherFiles, hasExternalReferences) - } - return evaluate -} - func isInfinityOrNaNString(name string) bool { return name == "Infinity" || name == "-Infinity" || name == "NaN" } @@ -1842,11 +1697,8 @@ func expressionResultIsUnused(node *ast.Node) bool { } } -func pseudoBigIntToString(value PseudoBigInt) string { - if value.negative && value.base10Value != "0" { - return "-" + value.base10Value - } - return value.base10Value +func pseudoBigIntToString(value jsnum.PseudoBigInt) string { + return value.String() } func getSuperContainer(node *ast.Node, stopOnFunctions bool) *ast.Node { diff --git a/internal/evaluator/evaluator.go b/internal/evaluator/evaluator.go new file mode 100644 index 0000000000..dbba778e0e --- /dev/null +++ b/internal/evaluator/evaluator.go @@ -0,0 +1,155 @@ +package evaluator + +import ( + "strings" + + "github.com/microsoft/typescript-go/internal/ast" + "github.com/microsoft/typescript-go/internal/core" + "github.com/microsoft/typescript-go/internal/jsnum" +) + +type Result struct { + Value any + IsSyntacticallyString bool + ResolvedOtherFiles bool + HasExternalReferences bool +} + +func NewResult(value any, isSyntacticallyString bool, resolvedOtherFiles bool, hasExternalReferences bool) Result { + return Result{value, isSyntacticallyString, resolvedOtherFiles, hasExternalReferences} +} + +type Evaluator func(expr *ast.Node, location *ast.Node) Result + +func NewEvaluator(evaluateEntity Evaluator, outerExpressionsToSkip ast.OuterExpressionKinds) Evaluator { + var evaluate Evaluator + evaluate = func(expr *ast.Node, location *ast.Node) Result { + isSyntacticallyString := false + resolvedOtherFiles := false + hasExternalReferences := false + // It's unclear when/whether we should consider skipping other kinds of outer expressions. + // Type assertions intentionally break evaluation when evaluating literal types, such as: + // type T = `one ${"two" as any} three`; // string + // But it's less clear whether such an assertion should break enum member evaluation: + // enum E { + // A = "one" as any + // } + // SatisfiesExpressions and non-null assertions seem to have even less reason to break + // emitting enum members as literals. However, these expressions also break Babel's + // evaluation (but not esbuild's), and the isolatedModules errors we give depend on + // our evaluation results, so we're currently being conservative so as to issue errors + // on code that might break Babel. + expr = ast.SkipOuterExpressions(expr, outerExpressionsToSkip|ast.OEKParentheses) + switch expr.Kind { + case ast.KindPrefixUnaryExpression: + result := evaluate(expr.AsPrefixUnaryExpression().Operand, location) + resolvedOtherFiles = result.ResolvedOtherFiles + hasExternalReferences = result.HasExternalReferences + if value, ok := result.Value.(jsnum.Number); ok { + switch expr.AsPrefixUnaryExpression().Operator { + case ast.KindPlusToken: + return Result{value, isSyntacticallyString, resolvedOtherFiles, hasExternalReferences} + case ast.KindMinusToken: + return Result{-value, isSyntacticallyString, resolvedOtherFiles, hasExternalReferences} + case ast.KindTildeToken: + return Result{value.BitwiseNOT(), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences} + } + } + case ast.KindBinaryExpression: + left := evaluate(expr.AsBinaryExpression().Left, location) + right := evaluate(expr.AsBinaryExpression().Right, location) + operator := expr.AsBinaryExpression().OperatorToken.Kind + isSyntacticallyString = (left.IsSyntacticallyString || right.IsSyntacticallyString) && expr.AsBinaryExpression().OperatorToken.Kind == ast.KindPlusToken + resolvedOtherFiles = left.ResolvedOtherFiles || right.ResolvedOtherFiles + hasExternalReferences = left.HasExternalReferences || right.HasExternalReferences + leftNum, leftIsNum := left.Value.(jsnum.Number) + rightNum, rightIsNum := right.Value.(jsnum.Number) + if leftIsNum && rightIsNum { + switch operator { + case ast.KindBarToken: + return Result{leftNum.BitwiseOR(rightNum), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences} + case ast.KindAmpersandToken: + return Result{leftNum.BitwiseAND(rightNum), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences} + case ast.KindGreaterThanGreaterThanToken: + return Result{leftNum.SignedRightShift(rightNum), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences} + case ast.KindGreaterThanGreaterThanGreaterThanToken: + return Result{leftNum.UnsignedRightShift(rightNum), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences} + case ast.KindLessThanLessThanToken: + return Result{leftNum.LeftShift(rightNum), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences} + case ast.KindCaretToken: + return Result{leftNum.BitwiseXOR(rightNum), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences} + case ast.KindAsteriskToken: + return Result{leftNum * rightNum, isSyntacticallyString, resolvedOtherFiles, hasExternalReferences} + case ast.KindSlashToken: + return Result{leftNum / rightNum, isSyntacticallyString, resolvedOtherFiles, hasExternalReferences} + case ast.KindPlusToken: + return Result{leftNum + rightNum, isSyntacticallyString, resolvedOtherFiles, hasExternalReferences} + case ast.KindMinusToken: + return Result{leftNum - rightNum, isSyntacticallyString, resolvedOtherFiles, hasExternalReferences} + case ast.KindPercentToken: + return Result{leftNum.Remainder(rightNum), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences} + case ast.KindAsteriskAsteriskToken: + return Result{leftNum.Exponentiate(rightNum), isSyntacticallyString, resolvedOtherFiles, hasExternalReferences} + } + } + leftStr, leftIsStr := left.Value.(string) + rightStr, rightIsStr := right.Value.(string) + if (leftIsStr || leftIsNum) && (rightIsStr || rightIsNum) && operator == ast.KindPlusToken { + if leftIsNum { + leftStr = leftNum.String() + } + if rightIsNum { + rightStr = rightNum.String() + } + return Result{leftStr + rightStr, isSyntacticallyString, resolvedOtherFiles, hasExternalReferences} + } + case ast.KindStringLiteral, ast.KindNoSubstitutionTemplateLiteral: + return Result{expr.Text(), true /*isSyntacticallyString*/, false, false} + case ast.KindTemplateExpression: + return evaluateTemplateExpression(expr, location, evaluate) + case ast.KindNumericLiteral: + return Result{jsnum.FromString(expr.Text()), false, false, false} + case ast.KindIdentifier: + return evaluateEntity(expr, location) + case ast.KindElementAccessExpression, ast.KindPropertyAccessExpression: + if ast.IsEntityNameExpression(expr.Expression()) { + return evaluateEntity(expr, location) + } + } + return Result{nil, isSyntacticallyString, resolvedOtherFiles, hasExternalReferences} + } + return evaluate +} + +func evaluateTemplateExpression(expr *ast.Node, location *ast.Node, evaluate Evaluator) Result { + var sb strings.Builder + sb.WriteString(expr.AsTemplateExpression().Head.Text()) + resolvedOtherFiles := false + hasExternalReferences := false + for _, span := range expr.AsTemplateExpression().TemplateSpans.Nodes { + spanResult := evaluate(span.Expression(), location) + if spanResult.Value == nil { + return Result{nil, true /*isSyntacticallyString*/, false, false} + } + sb.WriteString(AnyToString(spanResult.Value)) + sb.WriteString(span.AsTemplateSpan().Literal.Text()) + resolvedOtherFiles = resolvedOtherFiles || spanResult.ResolvedOtherFiles + hasExternalReferences = hasExternalReferences || spanResult.HasExternalReferences + } + return Result{sb.String(), true, resolvedOtherFiles, hasExternalReferences} +} + +func AnyToString(v any) string { + // !!! This function should behave identically to the expression `"" + v` in JS + switch v := v.(type) { + case string: + return v + case jsnum.Number: + return v.String() + case bool: + return core.IfElse(v, "true", "false") + case jsnum.PseudoBigInt: + return v.String() + } + panic("Unhandled case in anyToString") +} diff --git a/internal/jsnum/pseudobigint.go b/internal/jsnum/pseudobigint.go new file mode 100644 index 0000000000..4aa5ce12e8 --- /dev/null +++ b/internal/jsnum/pseudobigint.go @@ -0,0 +1,27 @@ +package jsnum + +// PseudoBigInt represents a JS-like bigint. +type PseudoBigInt struct { + Negative bool + Base10Value string +} + +func (value PseudoBigInt) String() string { + if len(value.Base10Value) == 0 || value.Base10Value == "0" { + return "0" + } + if value.Negative { + return "-" + value.Base10Value + } + return value.Base10Value +} + +func (value PseudoBigInt) Sign() int { + if len(value.Base10Value) == 0 || value.Base10Value == "0" { + return 0 + } + if value.Negative { + return -1 + } + return 1 +} diff --git a/internal/transformers/runtimesyntax.go b/internal/transformers/runtimesyntax.go index ee9bd1f7cd..c94b240dc9 100644 --- a/internal/transformers/runtimesyntax.go +++ b/internal/transformers/runtimesyntax.go @@ -10,6 +10,7 @@ import ( "github.com/microsoft/typescript-go/internal/ast" "github.com/microsoft/typescript-go/internal/binder" "github.com/microsoft/typescript-go/internal/core" + "github.com/microsoft/typescript-go/internal/evaluator" "github.com/microsoft/typescript-go/internal/jsnum" "github.com/microsoft/typescript-go/internal/printer" ) @@ -26,12 +27,11 @@ type RuntimeSyntaxTransformer struct { currentEnum *ast.EnumDeclarationNode currentNamespace *ast.ModuleDeclarationNode resolver binder.ReferenceResolver + evaluator evaluator.Evaluator + enumMemberCache map[*ast.EnumDeclarationNode]map[string]evaluator.Result } func NewRuntimeSyntaxTransformer(emitContext *printer.EmitContext, compilerOptions *core.CompilerOptions, resolver binder.ReferenceResolver) *Transformer { - if resolver == nil { - resolver = binder.NewReferenceResolver(binder.ReferenceResolverHooks{}) - } tx := &RuntimeSyntaxTransformer{compilerOptions: compilerOptions, resolver: resolver} return tx.newTransformer(tx.visit, emitContext) } @@ -381,6 +381,11 @@ func (tx *RuntimeSyntaxTransformer) transformEnumMember( memberNode := enum.Members.Nodes[index] member := memberNode.AsEnumMember() + var memberName string + if ast.IsIdentifier(member.Name()) || ast.IsStringLiteralLike(member.Name()) { + memberName = member.Name().Text() + } + savedParent := tx.parentNode tx.parentNode = tx.currentNode tx.currentNode = memberNode @@ -412,6 +417,9 @@ func (tx *RuntimeSyntaxTransformer) transformEnumMember( expression = constantExpression(*autoValue, tx.factory) if expression != nil { useExplicitReverseMapping = true + if len(memberName) > 0 { + tx.cacheEnumMemberValue(enum.AsNode(), memberName, evaluator.NewResult(*autoValue, false, false, false)) + } } else { expression = tx.factory.NewVoidExpression(tx.factory.NewNumericLiteral("0")) } @@ -420,14 +428,27 @@ func (tx *RuntimeSyntaxTransformer) transformEnumMember( // Enum members with an initializer may restore auto-numbering if the initializer is a numeric literal. If we // cannot syntactically determine the initializer value and the following enum member is auto-numbered, we will // use an `auto` variable to perform the remaining auto-numbering at runtime. + if tx.evaluator == nil { + tx.evaluator = evaluator.NewEvaluator(tx.evaluateEntity, ast.OEKAll) + } + var hasNumericInitializer, hasStringInitializer bool - switch value := constantValue(expression).(type) { + result := tx.evaluator(expression, enum.AsNode()) + switch value := result.Value.(type) { case jsnum.Number: hasNumericInitializer = true *autoValue = value + if !ast.IsNumericLiteral(expression) && !ast.IsSignedNumericLiteral(expression) { + expression = constantExpression(value, tx.factory) + } + tx.cacheEnumMemberValue(enum.AsNode(), memberName, result) case string: hasStringInitializer = true *autoValue = jsnum.NaN() + if !ast.IsStringLiteralLike(expression) { + expression = constantExpression(value, tx.factory) + } + tx.cacheEnumMemberValue(enum.AsNode(), memberName, result) default: *autoValue = jsnum.NaN() } @@ -999,6 +1020,9 @@ func (tx *RuntimeSyntaxTransformer) visitIdentifier(node *ast.IdentifierNode) *a func (tx *RuntimeSyntaxTransformer) visitExpressionIdentifier(node *ast.IdentifierNode) *ast.Node { if (tx.currentEnum != nil || tx.currentNamespace != nil) && !isGeneratedIdentifier(tx.emitContext, node) && !isLocalName(tx.emitContext, node) { location := tx.emitContext.MostOriginal(node.AsNode()) + if tx.resolver == nil { + tx.resolver = binder.NewReferenceResolver(binder.ReferenceResolverHooks{}) + } container := tx.resolver.GetReferencedExportContainer(location, false /*prefixLocals*/) if container != nil && (ast.IsEnumDeclaration(container) || ast.IsModuleDeclaration(container)) && container.Contains(location) { containerName := tx.getNamespaceContainerName(container) @@ -1044,6 +1068,52 @@ func (tx *RuntimeSyntaxTransformer) createExportStatement(name *ast.IdentifierNo return exportStatement } +func (tx *RuntimeSyntaxTransformer) cacheEnumMemberValue(enum *ast.EnumDeclarationNode, memberName string, result evaluator.Result) { + if tx.enumMemberCache == nil { + tx.enumMemberCache = make(map[*ast.EnumDeclarationNode]map[string]evaluator.Result) + } + memberCache := tx.enumMemberCache[enum] + if memberCache == nil { + memberCache = make(map[string]evaluator.Result) + tx.enumMemberCache[enum] = memberCache + } + memberCache[memberName] = result +} + +func (tx *RuntimeSyntaxTransformer) isReferenceToEnum(reference *ast.IdentifierNode, enum *ast.EnumDeclarationNode) bool { + if isGeneratedIdentifier(tx.emitContext, reference) { + originalEnum := tx.emitContext.MostOriginal(enum) + return tx.emitContext.GetNodeForGeneratedName(reference) == originalEnum + } + return reference.Text() == enum.Name().Text() +} + +func (tx *RuntimeSyntaxTransformer) evaluateEntity(node *ast.Node, location *ast.Node) evaluator.Result { + var result evaluator.Result + if ast.IsEnumDeclaration(location) { + memberCache := tx.enumMemberCache[location] + if memberCache != nil { + switch { + case ast.IsIdentifier(node): + result = memberCache[node.Text()] + case ast.IsPropertyAccessExpression(node): + access := node.AsPropertyAccessExpression() + expression := access.Expression + if ast.IsIdentifier(expression) && tx.isReferenceToEnum(expression, location) { + result = memberCache[access.Name().Text()] + } + case ast.IsElementAccessExpression(node): + access := node.AsElementAccessExpression() + expression := access.Expression + if ast.IsIdentifier(expression) && tx.isReferenceToEnum(expression, location) && ast.IsStringLiteralLike(access.ArgumentExpression) { + result = memberCache[access.ArgumentExpression.Text()] + } + } + } + } + return result +} + func getInnermostModuleDeclarationFromDottedModule(moduleDeclaration *ast.ModuleDeclaration) *ast.ModuleDeclaration { for moduleDeclaration.Body != nil && moduleDeclaration.Body.Kind == ast.KindModuleDeclaration { moduleDeclaration = moduleDeclaration.Body.AsModuleDeclaration() diff --git a/internal/transformers/runtimesyntax_test.go b/internal/transformers/runtimesyntax_test.go index d5a9d6153b..0a72777feb 100644 --- a/internal/transformers/runtimesyntax_test.go +++ b/internal/transformers/runtimesyntax_test.go @@ -128,20 +128,26 @@ func TestEnumTransformer(t *testing.T) { {title: "autonumber enum #14", input: "enum E {A,B,C=A|B,D}", output: `var E; (function (E) { - var auto; E[E["A"] = 0] = "A"; E[E["B"] = 1] = "B"; - E[E["C"] = auto = E.A | E.B] = "C"; - E[E["D"] = ++auto] = "D"; + E[E["C"] = 1] = "C"; + E[E["D"] = 2] = "D"; })(E || (E = {}));`}, - {title: "string enum", input: "enum E {A = 'x',B = 'y',C = 'z'}", output: `var E; + {title: "string enum #1", input: "enum E {A = 'x',B = 'y',C = 'z'}", output: `var E; (function (E) { E["A"] = 'x'; E["B"] = 'y'; E["C"] = 'z'; })(E || (E = {}));`}, + {title: "string enum #2", input: "enum E {A = 'x',B = 'y',C = `a${A}b${B}c`}", output: `var E; +(function (E) { + E["A"] = 'x'; + E["B"] = 'y'; + E["C"] = "axbyc"; +})(E || (E = {}));`}, + {title: "number enum", input: "enum E {A = 0,B = 1,C = 2}", output: `var E; (function (E) { E[E["A"] = 0] = "A"; @@ -152,8 +158,7 @@ func TestEnumTransformer(t *testing.T) { {title: "enum self reference #1", input: "enum E {A,B=A}", output: `var E; (function (E) { E[E["A"] = 0] = "A"; - E["B"] = E.A; - if (typeof E.B !== "string") E[E.B] = "B"; + E[E["B"] = 0] = "B"; })(E || (E = {}));`}, {title: "enum self reference #2", input: "enum E {A=x,B=A}", output: `var E; @@ -180,6 +185,12 @@ func TestEnumTransformer(t *testing.T) { if (typeof E["B "] !== "string") E[E["B "]] = "B "; })(E || (E = {}));`}, + {title: "enum self reference #5", input: "enum E {A,B=E.A}", output: `var E; +(function (E) { + E[E["A"] = 0] = "A"; + E[E["B"] = 0] = "B"; +})(E || (E = {}));`}, + {title: "export enum", input: "export enum E {A, B}", output: `export { E }; var E; (function (E) { @@ -210,8 +221,8 @@ var E; }`, output: `var E; (function (E) { E[E["A"] = 0] = "A"; - E[E["B"] = 1 << 0] = "B"; - E[E["C"] = 1 << 1] = "C"; + E[E["B"] = 1] = "B"; + E[E["C"] = 2] = "C"; E[E["D"] = 3] = "D"; })(E || (E = {}));`}, } diff --git a/internal/transformers/utilities.go b/internal/transformers/utilities.go index 2f1bb53b28..d6e9552c98 100644 --- a/internal/transformers/utilities.go +++ b/internal/transformers/utilities.go @@ -205,64 +205,6 @@ func isIdentifierReference(name *ast.IdentifierNode, parent *ast.Node) bool { } } -func constantValue(node *ast.Expression) any { - node = ast.SkipOuterExpressions(node, ast.OEKAll) - switch { - case ast.IsStringLiteralLike(node): - return node.Text() - - case ast.IsNumericLiteral(node): - return jsnum.FromString(node.Text()) - - case ast.IsPrefixUnaryExpression(node): - prefixUnary := node.AsPrefixUnaryExpression() - if value, ok := constantValue(prefixUnary.Operand).(jsnum.Number); ok { - switch prefixUnary.Operator { - case ast.KindPlusToken: - return value - case ast.KindMinusToken: - return -value - case ast.KindTildeToken: - return value.BitwiseNOT() - } - } - - case ast.IsBinaryExpression(node): - binary := node.AsBinaryExpression() - leftNum, leftIsNum := constantValue(binary.Left).(jsnum.Number) - rightNum, rightIsNum := constantValue(binary.Right).(jsnum.Number) - if leftIsNum && rightIsNum { - switch binary.OperatorToken.Kind { - case ast.KindBarToken: - return leftNum.BitwiseOR(rightNum) - case ast.KindAmpersandToken: - return leftNum.BitwiseAND(rightNum) - case ast.KindGreaterThanGreaterThanToken: - return leftNum.SignedRightShift(rightNum) - case ast.KindGreaterThanGreaterThanGreaterThanToken: - return leftNum.UnsignedRightShift(rightNum) - case ast.KindLessThanLessThanToken: - return leftNum.LeftShift(rightNum) - case ast.KindCaretToken: - return leftNum.BitwiseXOR(rightNum) - case ast.KindAsteriskToken: - return leftNum * rightNum - case ast.KindSlashToken: - return leftNum / rightNum - case ast.KindPlusToken: - return leftNum + rightNum - case ast.KindMinusToken: - return leftNum - rightNum - case ast.KindPercentToken: - return leftNum.Remainder(rightNum) - case ast.KindAsteriskAsteriskToken: - return leftNum.Exponentiate(rightNum) - } - } - } - return nil -} - func constantExpression(value any, factory *ast.NodeFactory) *ast.Expression { switch value := value.(type) { case string: