Skip to content

Commit a421961

Browse files
johnfav03Copilot
authored andcommitted
Organize Imports (#2331)
1 parent 7c551ae commit a421961

77 files changed

Lines changed: 4819 additions & 318 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

internal/checker/exports.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,14 @@ func (c *Checker) GetJsxNamespace(location *ast.Node) string {
175175
return c.getJsxNamespace(location)
176176
}
177177

178+
func (c *Checker) GetJsxFragmentFactory(location *ast.Node) string {
179+
entity := c.getJsxFragmentFactoryEntity(location)
180+
if entity != nil {
181+
return ast.GetFirstIdentifier(entity).Text()
182+
}
183+
return ""
184+
}
185+
178186
func (c *Checker) ResolveName(name string, location *ast.Node, meaning ast.SymbolFlags, excludeGlobals bool) *ast.Symbol {
179187
return c.resolveName(location, name, meaning, nil, true, excludeGlobals)
180188
}

internal/checker/printer.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ func (c *Checker) TypeToStringEx(t *Type, enclosingDeclaration *ast.Node, flags
182182
}
183183

184184
func (c *Checker) typeToStringEx(t *Type, enclosingDeclaration *ast.Node, flags TypeFormatFlags) string {
185-
writer := printer.NewTextWriter("")
185+
writer := printer.NewTextWriter("", 0)
186186
noTruncation := (c.compilerOptions.NoErrorTruncation == core.TSTrue) || (flags&TypeFormatFlagsNoTruncation != 0)
187187
combinedFlags := toNodeBuilderFlags(flags) | nodebuilder.FlagsIgnoreErrors
188188
if noTruncation {

internal/checker/services.go

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@ package checker
33
import (
44
"maps"
55
"slices"
6+
"strings"
67

78
"github.com/microsoft/typescript-go/internal/ast"
9+
"github.com/microsoft/typescript-go/internal/astnav"
810
"github.com/microsoft/typescript-go/internal/collections"
911
"github.com/microsoft/typescript-go/internal/core"
12+
"github.com/microsoft/typescript-go/internal/debug"
1013
"github.com/microsoft/typescript-go/internal/printer"
14+
"github.com/microsoft/typescript-go/internal/scanner"
1115
)
1216

1317
func (c *Checker) GetSymbolsInScope(location *ast.Node, meaning ast.SymbolFlags) []*ast.Symbol {
@@ -499,6 +503,146 @@ func (c *Checker) GetSymbolsOfParameterPropertyDeclaration(parameter *ast.Node /
499503
panic("There should exist two symbols, one as property declaration and one as parameter declaration")
500504
}
501505

506+
// IsDeclarationUsed checks if an import declaration identifier is used in the source file.
507+
// This is primarily used for organizing imports to determine which imports can be removed.
508+
func (c *Checker) IsDeclarationUsed(
509+
sourceFile *ast.SourceFile,
510+
identifier *ast.Identifier,
511+
jsxElementsPresent bool,
512+
jsxModeNeedsExplicitImport bool,
513+
) bool {
514+
if jsxElementsPresent && jsxModeNeedsExplicitImport {
515+
jsxNamespace := c.getJsxNamespace(sourceFile.AsNode())
516+
jsxFragmentFactory := c.GetJsxFragmentFactory(sourceFile.AsNode())
517+
identifierText := identifier.Text
518+
if identifierText == jsxNamespace {
519+
return true
520+
}
521+
if jsxFragmentFactory != "" && identifierText == jsxFragmentFactory {
522+
return true
523+
}
524+
}
525+
526+
symbol := c.GetSymbolAtLocation(identifier.AsNode())
527+
if symbol == nil {
528+
return true
529+
}
530+
531+
return c.IsSymbolReferencedInFile(sourceFile, identifier, symbol)
532+
}
533+
534+
// IsSymbolReferencedInFile checks if a symbol is referenced in the source file (besides its definition).
535+
// This is used as a quick check for whether a symbol is used at all in a file.
536+
func (c *Checker) IsSymbolReferencedInFile(
537+
sourceFile *ast.SourceFile,
538+
definition *ast.Identifier,
539+
symbol *ast.Symbol,
540+
) bool {
541+
identifierText := definition.Text
542+
for _, token := range getPossibleSymbolReferenceNodes(sourceFile, identifierText, sourceFile.AsNode()) {
543+
if !ast.IsIdentifier(token) {
544+
continue
545+
}
546+
id := token.AsIdentifier()
547+
if id == definition || id.Text != identifierText {
548+
continue
549+
}
550+
refSymbol := c.GetSymbolAtLocation(token)
551+
if refSymbol == symbol {
552+
return true
553+
}
554+
if token.Parent != nil && token.Parent.Kind == ast.KindShorthandPropertyAssignment {
555+
shorthandSymbol := c.GetShorthandAssignmentValueSymbol(token.Parent)
556+
if shorthandSymbol == symbol {
557+
return true
558+
}
559+
}
560+
if token.Parent != nil && ast.IsExportSpecifier(token.Parent) {
561+
localSymbol := c.getLocalSymbolForExportSpecifier(token.AsIdentifier(), refSymbol, token.Parent.AsExportSpecifier())
562+
if localSymbol == symbol {
563+
return true
564+
}
565+
}
566+
}
567+
return false
568+
}
569+
570+
func (c *Checker) getLocalSymbolForExportSpecifier(referenceLocation *ast.Identifier, referenceSymbol *ast.Symbol, exportSpecifier *ast.ExportSpecifier) *ast.Symbol {
571+
if isExportSpecifierAlias(referenceLocation, exportSpecifier) {
572+
if symbol := c.GetExportSpecifierLocalTargetSymbol(exportSpecifier.AsNode()); symbol != nil {
573+
return symbol
574+
}
575+
}
576+
return referenceSymbol
577+
}
578+
579+
func isExportSpecifierAlias(referenceLocation *ast.Identifier, exportSpecifier *ast.ExportSpecifier) bool {
580+
debug.Assert(exportSpecifier.PropertyName == referenceLocation.AsNode() || exportSpecifier.Name() == referenceLocation.AsNode(), "referenceLocation is not export specifier name or property name")
581+
propertyName := exportSpecifier.PropertyName
582+
if propertyName != nil {
583+
// Given `export { foo as bar } [from "someModule"]`: It's an alias at `foo`, but at `bar` it's a new symbol.
584+
return propertyName == referenceLocation.AsNode()
585+
} else {
586+
// `export { foo } from "foo"` is a re-export.
587+
// `export { foo };` is not a re-export, it creates an alias for the local variable `foo`.
588+
return exportSpecifier.Parent.Parent.ModuleSpecifier() == nil
589+
}
590+
}
591+
592+
func getPossibleSymbolReferenceNodes(sourceFile *ast.SourceFile, symbolName string, container *ast.Node) []*ast.Node {
593+
return core.MapNonNil(getPossibleSymbolReferencePositions(sourceFile, symbolName, container), func(pos int) *ast.Node {
594+
if referenceLocation := astnav.GetTouchingPropertyName(sourceFile, pos); referenceLocation != sourceFile.AsNode() {
595+
return referenceLocation
596+
}
597+
return nil
598+
})
599+
}
600+
601+
func getPossibleSymbolReferencePositions(sourceFile *ast.SourceFile, symbolName string, container *ast.Node) []int {
602+
positions := []int{}
603+
604+
// TODO: Cache symbol existence for files to save text search
605+
// Also, need to make this work for unicode escapes.
606+
607+
// Be resilient in the face of a symbol with no name or zero length name
608+
if symbolName == "" {
609+
return positions
610+
}
611+
612+
text := sourceFile.Text()
613+
sourceLength := len(text)
614+
symbolNameLength := len(symbolName)
615+
616+
if container == nil {
617+
container = sourceFile.AsNode()
618+
}
619+
620+
position := strings.Index(text[container.Pos():], symbolName)
621+
endPos := container.End()
622+
for position >= 0 && position < endPos {
623+
// We found a match. Make sure it's not part of a larger word (i.e. the char
624+
// before and after it have to be a non-identifier char).
625+
endPosition := position + symbolNameLength
626+
627+
if (position == 0 || !scanner.IsIdentifierPart(rune(text[position-1]))) &&
628+
(endPosition == sourceLength || !scanner.IsIdentifierPart(rune(text[endPosition]))) {
629+
// Found a real match. Keep searching.
630+
positions = append(positions, position)
631+
}
632+
startIndex := position + symbolNameLength + 1
633+
if startIndex > len(text) {
634+
break
635+
}
636+
if foundIndex := strings.Index(text[startIndex:], symbolName); foundIndex != -1 {
637+
position = startIndex + foundIndex
638+
} else {
639+
break
640+
}
641+
}
642+
643+
return positions
644+
}
645+
502646
func (c *Checker) GetTypeArgumentConstraint(node *ast.Node) *Type {
503647
if !ast.IsTypeNode(node) {
504648
return nil

internal/compiler/program.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1409,7 +1409,7 @@ func (p *Program) Emit(ctx context.Context, options EmitOptions) *EmitResult {
14091409
newLine := p.Options().NewLine.GetNewLineCharacter()
14101410
writerPool := &sync.Pool{
14111411
New: func() any {
1412-
return printer.NewTextWriter(newLine)
1412+
return printer.NewTextWriter(newLine, 0)
14131413
},
14141414
}
14151415
wg := core.NewWorkGroup(p.SingleThreaded())

0 commit comments

Comments
 (0)