Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
cfa6255
initial impl and todos
johnfav03 Dec 10, 2025
ee21821
Merge remote-tracking branch 'origin/main' into organize-imports
johnfav03 Jan 13, 2026
fc0e955
full impl before testing
johnfav03 Jan 13, 2026
be02da0
Merge remote-tracking branch 'origin/main' into organize-imports
johnfav03 Jan 22, 2026
419ff53
ported fourslash tests with no errors
johnfav03 Jan 22, 2026
6b63d9e
fixed linting issues
johnfav03 Jan 23, 2026
06e7ded
removed extraneous switch case
johnfav03 Jan 23, 2026
3e5607a
implemented copilot feedback
johnfav03 Jan 23, 2026
317d913
added consts to lsproto
johnfav03 Jan 23, 2026
be64ce6
updated import structure in organizeimports
johnfav03 Jan 23, 2026
f15fb78
generate multiline verify cmd
johnfav03 Jan 26, 2026
816a985
Merge remote-tracking branch 'origin/main' into organize-imports
johnfav03 Jan 26, 2026
f8fcce2
Merge remote-tracking branch 'origin/main' into organize-imports
johnfav03 Jan 27, 2026
73ccfbf
fixed package structure
johnfav03 Jan 27, 2026
87787fb
Merge remote-tracking branch 'origin/main' into organize-imports
johnfav03 Jan 27, 2026
88ad974
removed unused arg and moved tracker instantiation
johnfav03 Jan 27, 2026
0a40cb3
updated text condition in trackerimpl w comment
johnfav03 Jan 27, 2026
f1bf23f
Merge remote-tracking branch 'origin/main' into organize-imports
johnfav03 Jan 27, 2026
53a9769
updated to const and reupdated codeactionprovider
johnfav03 Jan 28, 2026
f788822
Merge remote-tracking branch 'origin/main' into organize-imports
johnfav03 Jan 28, 2026
c1524c4
fixed shebang emission
johnfav03 Jan 29, 2026
d6202f7
used indentsize instead of hardcoded string
johnfav03 Jan 30, 2026
8851383
Merge remote-tracking branch 'origin/main' into organize-imports
johnfav03 Jan 30, 2026
fc33fc7
Merge branch 'main' into organize-imports
johnfav03 Jan 30, 2026
eb197aa
Merge remote-tracking branch 'origin/main' into organize-imports
johnfav03 Feb 2, 2026
64e4fb8
remove extraneous print
johnfav03 Feb 3, 2026
1170fc8
added shebang test
johnfav03 Feb 3, 2026
ee5ecee
Merge remote-tracking branch 'origin/main' into organize-imports
johnfav03 Feb 3, 2026
9b236a7
moved isdeclarationused and issymbolreferencedinfile
johnfav03 Feb 4, 2026
6e6ad3c
moved isnewgroup and groupbycontiguousnewline
johnfav03 Feb 4, 2026
b114fcb
updated filterimportdecls to use core.filter
johnfav03 Feb 4, 2026
2a90731
exported RangeIsOnSingleLine in printer
johnfav03 Feb 4, 2026
08418d3
moved getimportattributeskey
johnfav03 Feb 4, 2026
d420f7a
chained variablestatement
johnfav03 Feb 4, 2026
80b0098
chained decl and initializer
johnfav03 Feb 4, 2026
42386d3
removed compareexportspecifiers
johnfav03 Feb 4, 2026
aa9ba2e
updated getnamedimportspecifiercomparerwithdetection comment
johnfav03 Feb 4, 2026
0607491
updated detectmodulespecifiercasebysort comment
johnfav03 Feb 4, 2026
b019ef9
updated getorganizeimportsstringcomparerwithdetection comment
johnfav03 Feb 4, 2026
6232fdf
updated default indentSize
johnfav03 Feb 4, 2026
bcb794a
used defaultindentsize in formatcodesettings
johnfav03 Feb 4, 2026
ac15fbf
removed userpreferences param
johnfav03 Feb 4, 2026
4324749
removed prefs nil checks
johnfav03 Feb 4, 2026
0da28ef
removed issorted
johnfav03 Feb 4, 2026
c9d7a44
added nil handling for modulespecifiercomparer
johnfav03 Feb 4, 2026
e2370c7
removed redundant if
johnfav03 Feb 4, 2026
22727aa
changed typechecker to param for removeunused
johnfav03 Feb 4, 2026
91d72a9
cloned oldimportdecls to prevent mutation
johnfav03 Feb 4, 2026
b140b02
Merge remote-tracking branch 'origin/main' into organize-imports
johnfav03 Feb 4, 2026
979e229
moved shebang test
johnfav03 Feb 4, 2026
883c765
registered removeunused and sortandcombine code actions
johnfav03 Feb 5, 2026
d49a2a8
Merge remote-tracking branch 'origin/main' into organize-imports
johnfav03 Feb 5, 2026
23d4d50
added removeunused multiline test
johnfav03 Feb 5, 2026
6adb203
changed attribute names in editor.codeactionsonsave
johnfav03 Feb 6, 2026
93d8f52
fixed linting
johnfav03 Feb 6, 2026
0abe026
Merge remote-tracking branch 'origin/main' into organize-imports
johnfav03 Feb 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions internal/checker/exports.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,14 @@ func (c *Checker) GetJsxNamespace(location *ast.Node) string {
return c.getJsxNamespace(location)
}

func (c *Checker) GetJsxFragmentFactory(location *ast.Node) string {
entity := c.getJsxFragmentFactoryEntity(location)
if entity != nil {
return ast.GetFirstIdentifier(entity).Text()
}
return ""
}

func (c *Checker) ResolveName(name string, location *ast.Node, meaning ast.SymbolFlags, excludeGlobals bool) *ast.Symbol {
return c.resolveName(location, name, meaning, nil, true, excludeGlobals)
}
Expand Down
2 changes: 1 addition & 1 deletion internal/checker/printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ func (c *Checker) TypeToStringEx(t *Type, enclosingDeclaration *ast.Node, flags
}

func (c *Checker) typeToStringEx(t *Type, enclosingDeclaration *ast.Node, flags TypeFormatFlags) string {
writer := printer.NewTextWriter("")
writer := printer.NewTextWriter("", 0)
noTruncation := (c.compilerOptions.NoErrorTruncation == core.TSTrue) || (flags&TypeFormatFlagsNoTruncation != 0)
combinedFlags := toNodeBuilderFlags(flags) | nodebuilder.FlagsIgnoreErrors
if noTruncation {
Expand Down
144 changes: 144 additions & 0 deletions internal/checker/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ package checker
import (
"maps"
"slices"
"strings"

"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/astnav"
"github.com/microsoft/typescript-go/internal/collections"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/debug"
"github.com/microsoft/typescript-go/internal/printer"
"github.com/microsoft/typescript-go/internal/scanner"
)

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

// IsDeclarationUsed checks if an import declaration identifier is used in the source file.
// This is primarily used for organizing imports to determine which imports can be removed.
func (c *Checker) IsDeclarationUsed(
sourceFile *ast.SourceFile,
identifier *ast.Identifier,
jsxElementsPresent bool,
jsxModeNeedsExplicitImport bool,
) bool {
if jsxElementsPresent && jsxModeNeedsExplicitImport {
jsxNamespace := c.getJsxNamespace(sourceFile.AsNode())
jsxFragmentFactory := c.GetJsxFragmentFactory(sourceFile.AsNode())
identifierText := identifier.Text
if identifierText == jsxNamespace {
return true
}
if jsxFragmentFactory != "" && identifierText == jsxFragmentFactory {
return true
}
}

symbol := c.GetSymbolAtLocation(identifier.AsNode())
if symbol == nil {
return true
}

return c.IsSymbolReferencedInFile(sourceFile, identifier, symbol)
}

// IsSymbolReferencedInFile checks if a symbol is referenced in the source file (besides its definition).
// This is used as a quick check for whether a symbol is used at all in a file.
func (c *Checker) IsSymbolReferencedInFile(
sourceFile *ast.SourceFile,
definition *ast.Identifier,
symbol *ast.Symbol,
) bool {
identifierText := definition.Text
for _, token := range getPossibleSymbolReferenceNodes(sourceFile, identifierText, sourceFile.AsNode()) {
if !ast.IsIdentifier(token) {
continue
}
id := token.AsIdentifier()
if id == definition || id.Text != identifierText {
continue
}
refSymbol := c.GetSymbolAtLocation(token)
if refSymbol == symbol {
return true
}
if token.Parent != nil && token.Parent.Kind == ast.KindShorthandPropertyAssignment {
shorthandSymbol := c.GetShorthandAssignmentValueSymbol(token.Parent)
if shorthandSymbol == symbol {
return true
}
}
if token.Parent != nil && ast.IsExportSpecifier(token.Parent) {
localSymbol := c.getLocalSymbolForExportSpecifier(token.AsIdentifier(), refSymbol, token.Parent.AsExportSpecifier())
if localSymbol == symbol {
return true
}
}
}
return false
}

func (c *Checker) getLocalSymbolForExportSpecifier(referenceLocation *ast.Identifier, referenceSymbol *ast.Symbol, exportSpecifier *ast.ExportSpecifier) *ast.Symbol {
if isExportSpecifierAlias(referenceLocation, exportSpecifier) {
if symbol := c.GetExportSpecifierLocalTargetSymbol(exportSpecifier.AsNode()); symbol != nil {
return symbol
}
}
return referenceSymbol
}

func isExportSpecifierAlias(referenceLocation *ast.Identifier, exportSpecifier *ast.ExportSpecifier) bool {
debug.Assert(exportSpecifier.PropertyName == referenceLocation.AsNode() || exportSpecifier.Name() == referenceLocation.AsNode(), "referenceLocation is not export specifier name or property name")
propertyName := exportSpecifier.PropertyName
if propertyName != nil {
// Given `export { foo as bar } [from "someModule"]`: It's an alias at `foo`, but at `bar` it's a new symbol.
return propertyName == referenceLocation.AsNode()
} else {
// `export { foo } from "foo"` is a re-export.
// `export { foo };` is not a re-export, it creates an alias for the local variable `foo`.
return exportSpecifier.Parent.Parent.ModuleSpecifier() == nil
}
}

func getPossibleSymbolReferenceNodes(sourceFile *ast.SourceFile, symbolName string, container *ast.Node) []*ast.Node {
return core.MapNonNil(getPossibleSymbolReferencePositions(sourceFile, symbolName, container), func(pos int) *ast.Node {
if referenceLocation := astnav.GetTouchingPropertyName(sourceFile, pos); referenceLocation != sourceFile.AsNode() {
return referenceLocation
}
return nil
})
}

func getPossibleSymbolReferencePositions(sourceFile *ast.SourceFile, symbolName string, container *ast.Node) []int {
positions := []int{}

// TODO: Cache symbol existence for files to save text search
// Also, need to make this work for unicode escapes.

// Be resilient in the face of a symbol with no name or zero length name
if symbolName == "" {
return positions
}

text := sourceFile.Text()
sourceLength := len(text)
symbolNameLength := len(symbolName)

if container == nil {
container = sourceFile.AsNode()
}

position := strings.Index(text[container.Pos():], symbolName)
endPos := container.End()
for position >= 0 && position < endPos {
// We found a match. Make sure it's not part of a larger word (i.e. the char
// before and after it have to be a non-identifier char).
endPosition := position + symbolNameLength

if (position == 0 || !scanner.IsIdentifierPart(rune(text[position-1]))) &&
(endPosition == sourceLength || !scanner.IsIdentifierPart(rune(text[endPosition]))) {
// Found a real match. Keep searching.
positions = append(positions, position)
}
startIndex := position + symbolNameLength + 1
if startIndex > len(text) {
break
}
if foundIndex := strings.Index(text[startIndex:], symbolName); foundIndex != -1 {
position = startIndex + foundIndex
} else {
break
}
}

return positions
}

func (c *Checker) GetTypeArgumentConstraint(node *ast.Node) *Type {
if !ast.IsTypeNode(node) {
return nil
Expand Down
2 changes: 1 addition & 1 deletion internal/compiler/program.go
Original file line number Diff line number Diff line change
Expand Up @@ -1409,7 +1409,7 @@ func (p *Program) Emit(ctx context.Context, options EmitOptions) *EmitResult {
newLine := p.Options().NewLine.GetNewLineCharacter()
writerPool := &sync.Pool{
New: func() any {
return printer.NewTextWriter(newLine)
return printer.NewTextWriter(newLine, 0)
},
}
wg := core.NewWorkGroup(p.SingleThreaded())
Expand Down
Loading