@@ -3,11 +3,15 @@ package checker
33import (
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
1317func (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+
502646func (c * Checker ) GetTypeArgumentConstraint (node * ast.Node ) * Type {
503647 if ! ast .IsTypeNode (node ) {
504648 return nil
0 commit comments