Skip to content

Port JSX Emit #1159

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jun 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
13 changes: 13 additions & 0 deletions internal/ast/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -3609,3 +3609,16 @@ func (h *hasFileNameImpl) FileName() string {
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
}
})
}
31 changes: 27 additions & 4 deletions internal/checker/emitresolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ import (

var _ printer.EmitResolver = &emitResolver{}

// Links for jsx
type JSXLinks struct {
importRef *ast.Node
}

// Links for declarations

type DeclarationLinks struct {
Expand All @@ -31,10 +36,23 @@ type emitResolver struct {
checkerMu sync.Mutex
isValueAliasDeclaration func(node *ast.Node) bool
referenceResolver binder.ReferenceResolver
jsxLinks core.LinkStore[*ast.Node, JSXLinks]
declarationLinks core.LinkStore[*ast.Node, DeclarationLinks]
declarationFileLinks core.LinkStore[*ast.Node, DeclarationFileLinks]
}

func (r *emitResolver) GetJsxFactoryEntity(location *ast.Node) *ast.Node {
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
return r.checker.getJsxFactoryEntity(location)
}

func (r *emitResolver) GetJsxFragmentFactoryEntity(location *ast.Node) *ast.Node {
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
return r.checker.getJsxFragmentFactoryEntity(location)
}

func (r *emitResolver) IsOptionalParameter(node *ast.Node) bool {
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
Expand Down Expand Up @@ -786,13 +804,18 @@ func (r *emitResolver) GetReferencedExportContainer(node *ast.IdentifierNode, pr
return r.getReferenceResolver().GetReferencedExportContainer(node, prefixLocals)
}

func (r *emitResolver) GetReferencedImportDeclaration(node *ast.IdentifierNode) *ast.Declaration {
if !ast.IsParseTreeNode(node) {
return nil
}
func (r *emitResolver) SetReferencedImportDeclaration(node *ast.IdentifierNode, ref *ast.Declaration) {
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
r.jsxLinks.Get(node).importRef = ref
}

func (r *emitResolver) GetReferencedImportDeclaration(node *ast.IdentifierNode) *ast.Declaration {
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
if !ast.IsParseTreeNode(node) {
return r.jsxLinks.Get(node).importRef
}

return r.getReferenceResolver().GetReferencedImportDeclaration(node)
}
Expand Down
2 changes: 1 addition & 1 deletion internal/checker/grammarchecks.go
Original file line number Diff line number Diff line change
Expand Up @@ -1195,7 +1195,7 @@ func (c *Checker) checkGrammarJsxName(node *ast.JsxTagNameExpression) bool {
return c.grammarErrorOnNode(node.Expression(), diagnostics.JSX_property_access_expressions_cannot_include_JSX_namespace_names)
}

if ast.IsJsxNamespacedName(node) && c.compilerOptions.GetJSXTransformEnabled() && !IsIntrinsicJsxName(node.AsJsxNamespacedName().Namespace.Text()) {
if ast.IsJsxNamespacedName(node) && c.compilerOptions.GetJSXTransformEnabled() && !scanner.IsIntrinsicJsxName(node.AsJsxNamespacedName().Namespace.Text()) {
return c.grammarErrorOnNode(node, diagnostics.React_components_cannot_include_JSX_namespace_names)
}

Expand Down
21 changes: 4 additions & 17 deletions internal/checker/jsx.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ func (c *Checker) getContextualTypeForChildJsxExpression(node *ast.Node, child *
if !(attributesType != nil && !IsTypeAny(attributesType) && jsxChildrenPropertyName != ast.InternalSymbolNameMissing && jsxChildrenPropertyName != "") {
return nil
}
realChildren := getSemanticJsxChildren(node.Children().Nodes)
realChildren := ast.GetSemanticJsxChildren(node.Children().Nodes)
childIndex := slices.Index(realChildren, child)
childFieldType := c.getTypeOfPropertyOfContextualType(attributesType, jsxChildrenPropertyName)
if childFieldType == nil {
Expand Down Expand Up @@ -279,7 +279,7 @@ func (c *Checker) discriminateContextualTypeByJSXAttributes(node *ast.Node, cont
return false
}
element := node.Parent.Parent
if s.Name == jsxChildrenPropertyName && ast.IsJsxElement(element) && len(getSemanticJsxChildren(element.Children().Nodes)) != 0 {
if s.Name == jsxChildrenPropertyName && ast.IsJsxElement(element) && len(ast.GetSemanticJsxChildren(element.Children().Nodes)) != 0 {
return false
}
return node.Symbol().Members[s.Name] == nil && c.isDiscriminantProperty(contextualType, s.Name)
Expand Down Expand Up @@ -308,7 +308,7 @@ func (c *Checker) elaborateJsxComponents(node *ast.Node, source *Type, target *T
}
childrenNameType := c.getStringLiteralType(childrenPropName)
childrenTargetType := c.getIndexedAccessType(target, childrenNameType)
validChildren := getSemanticJsxChildren(containingElement.Children().Nodes)
validChildren := ast.GetSemanticJsxChildren(containingElement.Children().Nodes)
if len(validChildren) == 0 {
return reportedError
}
Expand Down Expand Up @@ -803,7 +803,7 @@ func (c *Checker) createJsxAttributesTypeFromAttributesProperty(openingLikeEleme
children = parent.AsJsxFragment().Children.Nodes
}
}
return len(getSemanticJsxChildren(children)) != 0
return len(ast.GetSemanticJsxChildren(children)) != 0
}
if parentHasSemanticJsxChildren(openingLikeElement) {
var childTypes []*Type = c.checkJsxChildren(openingLikeElement.Parent, checkMode)
Expand Down Expand Up @@ -855,19 +855,6 @@ func (c *Checker) createJsxAttributesTypeFromAttributesProperty(openingLikeEleme
return spread
}

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

func (c *Checker) checkJsxAttribute(node *ast.Node, checkMode CheckMode) *Type {
if node.Initializer() != nil {
return c.checkExpressionForMutableLocation(node.Initializer(), checkMode)
Expand Down
6 changes: 1 addition & 5 deletions internal/checker/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,6 @@ func NewDiagnosticChainForNode(chain *ast.Diagnostic, node *ast.Node, message *d
return NewDiagnosticForNode(node, message, args...)
}

func IsIntrinsicJsxName(name string) bool {
return len(name) != 0 && (name[0] >= 'a' && name[0] <= 'z' || strings.ContainsRune(name, '-'))
}

func findInMap[K comparable, V any](m map[K]V, predicate func(V) bool) V {
for _, value := range m {
if predicate(value) {
Expand Down Expand Up @@ -1337,7 +1333,7 @@ func isInRightSideOfImportOrExportAssignment(node *ast.EntityName) bool {
}

func isJsxIntrinsicTagName(tagName *ast.Node) bool {
return ast.IsIdentifier(tagName) && IsIntrinsicJsxName(tagName.Text()) || ast.IsJsxNamespacedName(tagName)
return ast.IsIdentifier(tagName) && scanner.IsIntrinsicJsxName(tagName.Text()) || ast.IsJsxNamespacedName(tagName)
}

func getContainingObjectLiteral(f *ast.SignatureDeclaration) *ast.Node {
Expand Down
2 changes: 1 addition & 1 deletion internal/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ func walkTreeForJSXTags(node *ast.Node) *ast.Node {
return true
}
if node.SubtreeFacts()&ast.SubtreeContainsJsx == 0 {
return true
return false
}
if ast.IsJsxOpeningElement(node) || ast.IsJsxFragment(node) {
found = node
Expand Down
5 changes: 5 additions & 0 deletions internal/printer/emitresolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ type EmitResolver interface {
GetEffectiveDeclarationFlags(node *ast.Node, flags ast.ModifierFlags) ast.ModifierFlags
GetResolutionModeOverride(node *ast.Node) core.ResolutionMode

// JSX Emit
GetJsxFactoryEntity(location *ast.Node) *ast.Node
GetJsxFragmentFactoryEntity(location *ast.Node) *ast.Node
SetReferencedImportDeclaration(node *ast.IdentifierNode, ref *ast.Declaration) // for overriding the reference resolver behavior for generated identifiers

// declaration emit checker functionality projections
PrecalculateDeclarationEmitVisibility(file *ast.SourceFile)
IsSymbolAccessible(symbol *ast.Symbol, enclosingDeclaration *ast.Node, meaning ast.SymbolFlags, shouldComputeAliasToMarkVisible bool) SymbolAccessibilityResult
Expand Down
15 changes: 15 additions & 0 deletions internal/printer/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,21 @@ func (f *NodeFactory) NewDisposeResourcesHelper(envBinding *ast.Expression) *ast

// !!! Class Fields Helpers
// !!! ES2018 Helpers
// Chains a sequence of expressions using the __assign helper or Object.assign if available in the target
func (f *NodeFactory) NewAssignHelper(attributesSegments []*ast.Expression, scriptTarget core.ScriptTarget) *ast.Expression {
if scriptTarget >= core.ScriptTargetES2015 {
return f.NewCallExpression(f.NewPropertyAccessExpression(f.NewIdentifier("Object"), nil, f.NewIdentifier("assign"), ast.NodeFlagsNone), nil, nil, f.NewNodeList(attributesSegments), ast.NodeFlagsNone)
}
f.emitContext.RequestEmitHelper(assignHelper)
return f.NewCallExpression(
f.NewUnscopedHelperName("__assign"),
nil,
nil,
f.NewNodeList(attributesSegments),
ast.NodeFlagsNone,
)
}

// !!! ES2018 Destructuring Helpers
// !!! ES2017 Helpers

Expand Down
18 changes: 18 additions & 0 deletions internal/printer/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,24 @@ var disposeResourcesHelper = &EmitHelper{

// !!! Class Fields Helpers
// !!! ES2018 Helpers
var assignHelper = &EmitHelper{
Name: "typescript:assign",
ImportName: "__assign",
Scoped: false,
Priority: &Priority{1},
Text: `var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};`,
}

// !!! ES2018 Destructuring Helpers
// !!! ES2017 Helpers

Expand Down
5 changes: 5 additions & 0 deletions internal/scanner/utilities.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package scanner

import (
"strings"
"unicode/utf8"

"github.com/microsoft/typescript-go/internal/ast"
Expand Down Expand Up @@ -60,3 +61,7 @@ func IsIdentifierText(name string, languageVersion core.ScriptTarget, languageVa
}
return true
}

func IsIntrinsicJsxName(name string) bool {
return len(name) != 0 && (name[0] >= 'a' && name[0] <= 'z' || strings.ContainsRune(name, '-'))
}
2 changes: 1 addition & 1 deletion internal/testutil/tsbaseline/type_symbol_baseline.go
Original file line number Diff line number Diff line change
Expand Up @@ -481,5 +481,5 @@ func isIntrinsicJsxTag(node *ast.Node, sourceFile *ast.SourceFile) bool {
return false
}
text := scanner.GetSourceTextOfNodeFromSourceFile(sourceFile, node, false /*includeTrivia*/)
return checker.IsIntrinsicJsxName(text)
return scanner.IsIntrinsicJsxName(text)
}
Loading
Loading