Skip to content

autoimport completions #1553

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

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions internal/ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -992,10 +992,22 @@ func (n *Node) ModuleSpecifier() *Expression {
return n.AsImportDeclaration().ModuleSpecifier
case KindExportDeclaration:
return n.AsExportDeclaration().ModuleSpecifier
case KindJSDocImportTag:
return n.AsJSDocImportTag().ModuleSpecifier
}
panic("Unhandled case in Node.ModuleSpecifier: " + n.Kind.String())
}

func (n *Node) ImportClause() *Node {
switch n.Kind {
case KindImportDeclaration:
return n.AsImportDeclaration().ImportClause
case KindJSDocImportTag:
return n.AsJSDocImportTag().ImportClause
}
panic("Unhandled case in Node.ImportClause: " + n.Kind.String())
}

func (n *Node) Statement() *Statement {
switch n.Kind {
case KindDoStatement:
Expand Down
19 changes: 19 additions & 0 deletions internal/ast/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -1408,6 +1408,16 @@ func GetNameOfDeclaration(declaration *Node) *Node {
return nil
}

func GetImportClauseOfDeclaration(declaration *Declaration) *ImportClause {
switch declaration.Kind {
case KindImportDeclaration:
return declaration.AsImportDeclaration().ImportClause.AsImportClause()
case KindJSDocImportTag:
return declaration.AsJSDocImportTag().ImportClause.AsImportClause()
}
return nil
}

func GetNonAssignedNameOfDeclaration(declaration *Node) *Node {
// !!!
switch declaration.Kind {
Expand Down Expand Up @@ -2722,6 +2732,15 @@ func IsRequireCall(node *Node, requireStringLiteralLikeArgument bool) bool {
return !requireStringLiteralLikeArgument || IsStringLiteralLike(call.Arguments.Nodes[0])
}

func IsRequireVariableStatement(node *Node) bool {
if IsVariableStatement(node) {
if declarations := node.AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes; len(declarations) > 0 {
return core.Every(declarations, IsVariableDeclarationInitializedToRequire)
}
}
return false
}

func GetJSXImplicitImportBase(compilerOptions *core.CompilerOptions, file *SourceFile) string {
jsxImportSourcePragma := GetPragmaFromSourceFile(file, "jsximportsource")
jsxRuntimePragma := GetPragmaFromSourceFile(file, "jsxruntime")
Expand Down
20 changes: 20 additions & 0 deletions internal/checker/exports.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,26 @@ func (c *Checker) GetMergedSymbol(symbol *ast.Symbol) *ast.Symbol {
return c.getMergedSymbol(symbol)
}

func (c *Checker) TryFindAmbientModule(moduleName string) *ast.Symbol {
return c.tryFindAmbientModule(moduleName, true /* withAugmentations */)
}

func (c *Checker) GetImmediateAliasedSymbol(symbol *ast.Symbol) *ast.Symbol {
return c.getImmediateAliasedSymbol(symbol)
}

func (c *Checker) GetTypeOnlyAliasDeclaration(symbol *ast.Symbol) *ast.Node {
return c.getTypeOnlyAliasDeclaration(symbol)
}

func (c *Checker) ResolveExternalModuleName(moduleSpecifier *ast.Node) *ast.Symbol {
return c.resolveExternalModuleName(moduleSpecifier, moduleSpecifier, true /*ignoreErrors*/)
}

func (c *Checker) ResolveExternalModuleSymbol(moduleSymbol *ast.Symbol) *ast.Symbol {
return c.resolveExternalModuleSymbol(moduleSymbol, false /*dontResolveAlias*/)
}

func (c *Checker) GetTypeFromTypeNode(node *ast.Node) *Type {
return c.getTypeFromTypeNode(node)
}
Expand Down
5 changes: 3 additions & 2 deletions internal/checker/nodebuilderimpl.go
Original file line number Diff line number Diff line change
Expand Up @@ -1079,7 +1079,7 @@ func canHaveModuleSpecifier(node *ast.Node) bool {
return false
}

func tryGetModuleSpecifierFromDeclaration(node *ast.Node) *ast.Node {
func TryGetModuleSpecifierFromDeclaration(node *ast.Node) *ast.Node {
res := tryGetModuleSpecifierFromDeclarationWorker(node)
if res == nil || !ast.IsStringLiteral(res) {
return nil
Expand Down Expand Up @@ -1165,7 +1165,7 @@ func (b *nodeBuilderImpl) getSpecifierForModuleSymbol(symbol *ast.Symbol, overri
enclosingDeclaration := b.e.MostOriginal(b.ctx.enclosingDeclaration)
var originalModuleSpecifier *ast.Node
if canHaveModuleSpecifier(enclosingDeclaration) {
originalModuleSpecifier = tryGetModuleSpecifierFromDeclaration(enclosingDeclaration)
originalModuleSpecifier = TryGetModuleSpecifierFromDeclaration(enclosingDeclaration)
}
contextFile := b.ctx.enclosingFile
resolutionMode := overrideImportMode
Expand Down Expand Up @@ -1216,6 +1216,7 @@ func (b *nodeBuilderImpl) getSpecifierForModuleSymbol(symbol *ast.Symbol, overri
modulespecifiers.ModuleSpecifierOptions{
OverrideImportMode: overrideImportMode,
},
false, /*forAutoImports*/
)
specifier := allSpecifiers[0]
links.specifierCache[cacheKey] = specifier
Expand Down
36 changes: 36 additions & 0 deletions internal/checker/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,35 @@ func (c *Checker) GetExportsOfModule(symbol *ast.Symbol) []*ast.Symbol {
return symbolsToArray(c.getExportsOfModule(symbol))
}

func (c *Checker) ForEachExportAndPropertyOfModule(moduleSymbol *ast.Symbol, cb func(*ast.Symbol, string)) {
for key, exportedSymbol := range c.getExportsOfModule(moduleSymbol) {
if !isReservedMemberName(key) {
cb(exportedSymbol, key)
}
}

exportEquals := c.resolveExternalModuleSymbol(moduleSymbol, false /*dontResolveAlias*/)
if exportEquals == moduleSymbol {
return
}

typeOfSymbol := c.getTypeOfSymbol(exportEquals)
if !c.shouldTreatPropertiesOfExternalModuleAsExports(typeOfSymbol) {
return
}

// forEachPropertyOfType
reducedType := c.getReducedApparentType(typeOfSymbol)
if reducedType.flags&TypeFlagsStructuredType == 0 {
return
}
for name, symbol := range c.resolveStructuredTypeMembers(reducedType).members {
if c.isNamedMember(symbol, name) {
cb(symbol, name)
}
}
}

func (c *Checker) IsValidPropertyAccess(node *ast.Node, propertyName string) bool {
return c.isValidPropertyAccess(node, propertyName)
}
Expand Down Expand Up @@ -345,6 +374,13 @@ func runWithoutResolvedSignatureCaching[T any](c *Checker, node *ast.Node, fn fu
return fn()
}

func (c *Checker) SkipAlias(symbol *ast.Symbol) *ast.Symbol {
if symbol.Flags&ast.SymbolFlagsAlias != 0 {
return c.GetAliasedSymbol(symbol)
}
return symbol
}

func (c *Checker) GetRootSymbols(symbol *ast.Symbol) []*ast.Symbol {
roots := c.getImmediateRootSymbols(symbol)
if roots != nil {
Expand Down
4 changes: 4 additions & 0 deletions internal/compiler/program.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,10 @@ func (p *Program) UseCaseSensitiveFileNames() bool {
return p.Host().FS().UseCaseSensitiveFileNames()
}

func (p *Program) UsesUriStyleNodeCoreModules() bool {
return p.usesUriStyleNodeCoreModules.IsTrue()
}

var _ checker.Program = (*Program)(nil)

/** This should have similar behavior to 'processSourceFile' without diagnostics or mutation. */
Expand Down
11 changes: 11 additions & 0 deletions internal/core/compileroptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,17 @@ const (
NewLineKindLF NewLineKind = 2
)

func GetNewLineKind(s string) NewLineKind {
switch s {
case "\r\n":
return NewLineKindCRLF
case "\n":
return NewLineKindLF
default:
return NewLineKindNone
}
}

func (newLine NewLineKind) GetNewLineCharacter() string {
switch newLine {
case NewLineKindCRLF:
Expand Down
11 changes: 11 additions & 0 deletions internal/core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,17 @@ func Every[T any](slice []T, f func(T) bool) bool {
return true
}

func Or[T any](funcs ...func(T) bool) func(T) bool {
return func(input T) bool {
for _, f := range funcs {
if f(input) {
return true
}
}
return false
}
}

func Find[T any](slice []T, f func(T) bool) T {
for _, value := range slice {
if f(value) {
Expand Down
4 changes: 2 additions & 2 deletions internal/core/nodemodules.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ var ExclusivelyPrefixedNodeCoreModules = map[string]bool{
"node:test/reporters": true,
}

var nodeCoreModules = sync.OnceValue(func() map[string]bool {
var NodeCoreModules = sync.OnceValue(func() map[string]bool {
nodeCoreModules := make(map[string]bool, len(UnprefixedNodeCoreModules)*2+len(ExclusivelyPrefixedNodeCoreModules))
for unprefixed := range UnprefixedNodeCoreModules {
nodeCoreModules[unprefixed] = true
Expand All @@ -81,7 +81,7 @@ var nodeCoreModules = sync.OnceValue(func() map[string]bool {
})

func NonRelativeModuleNameForTypingCache(moduleName string) string {
if nodeCoreModules()[moduleName] {
if NodeCoreModules()[moduleName] {
return "node"
}
return moduleName
Expand Down
20 changes: 20 additions & 0 deletions internal/core/textchange.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package core

import "strings"

type TextChange struct {
TextRange
NewText string
Expand All @@ -8,3 +10,21 @@ type TextChange struct {
func (t TextChange) ApplyTo(text string) string {
return text[:t.Pos()] + t.NewText + text[t.End():]
}

func ApplyBulkEdits(text string, edits []TextChange) string {
b := strings.Builder{}
b.Grow(len(text))
lastEnd := 0
for _, e := range edits {
start := e.TextRange.Pos()
if start != lastEnd {
b.WriteString(text[lastEnd:e.TextRange.Pos()])
}
b.WriteString(e.NewText)

lastEnd = e.TextRange.End()
}
b.WriteString(text[lastEnd:])

return b.String()
}
20 changes: 1 addition & 19 deletions internal/execute/tsc.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,6 @@ import (
"github.com/microsoft/typescript-go/internal/tspath"
)

func applyBulkEdits(text string, edits []core.TextChange) string {
b := strings.Builder{}
b.Grow(len(text))
lastEnd := 0
for _, e := range edits {
start := e.TextRange.Pos()
if start != lastEnd {
b.WriteString(text[lastEnd:e.TextRange.Pos()])
}
b.WriteString(e.NewText)

lastEnd = e.TextRange.End()
}
b.WriteString(text[lastEnd:])

return b.String()
}

type CommandLineResult struct {
Status ExitStatus
IncrementalProgram *incremental.Program
Expand Down Expand Up @@ -85,7 +67,7 @@ func fmtMain(sys System, input, output string) ExitStatus {
JSDocParsingMode: ast.JSDocParsingModeParseAll,
}, text, core.GetScriptKindFromFileName(string(pathified)))
edits := format.FormatDocument(ctx, sourceFile)
newText := applyBulkEdits(text, edits)
newText := core.ApplyBulkEdits(text, edits)

if err := sys.FS().WriteFile(output, newText, false); err != nil {
fmt.Fprintln(sys.Writer(), err.Error())
Expand Down
19 changes: 19 additions & 0 deletions internal/format/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,25 @@ func FormatSpan(ctx context.Context, span core.TextRange, file *ast.SourceFile,
)
}

func FormatNodeGivenIndentation(ctx context.Context, node *ast.Node, file *ast.SourceFile, languageVariant core.LanguageVariant, initialIndentation int, delta int) []core.TextChange {
textRange := core.NewTextRange(node.Pos(), node.End())
return newFormattingScanner(
file.Text(),
languageVariant,
textRange.Pos(),
textRange.End(),
newFormatSpanWorker(
ctx,
textRange,
node,
initialIndentation,
delta,
FormatRequestKindFormatSelection,
func(core.TextRange) bool { return false }, // assume that node does not have any errors
file,
))
}

func formatNodeLines(ctx context.Context, sourceFile *ast.SourceFile, node *ast.Node, requestKind FormatRequestKind) []core.TextChange {
if node == nil {
return nil
Expand Down
12 changes: 6 additions & 6 deletions internal/format/indent.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func getIndentationForNodeWorker(
if useActualIndentation {
// check if current node is a list item - if yes, take indentation from it
var firstListChild *ast.Node
containerList := getContainingList(current, sourceFile)
containerList := GetContainingList(current, sourceFile)
if containerList != nil {
firstListChild = core.FirstOrNil(containerList.Nodes)
}
Expand Down Expand Up @@ -139,7 +139,7 @@ func getActualIndentationForListItem(node *ast.Node, sourceFile *ast.SourceFile,
// VariableDeclarationList has no wrapping tokens
return -1
}
containingList := getContainingList(node, sourceFile)
containingList := GetContainingList(node, sourceFile)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't know this func existed; I wonder if it's general enough to use for sig help. (No action needed, just opining)

if containingList != nil {
index := core.FindIndex(containingList.Nodes, func(e *ast.Node) bool { return e == node })
if index != -1 {
Expand Down Expand Up @@ -196,10 +196,10 @@ func deriveActualIndentationFromList(list *ast.NodeList, index int, sourceFile *

func findColumnForFirstNonWhitespaceCharacterInLine(line int, char int, sourceFile *ast.SourceFile, options *FormatCodeSettings) int {
lineStart := scanner.GetPositionOfLineAndCharacter(sourceFile, line, 0)
return findFirstNonWhitespaceColumn(lineStart, lineStart+char, sourceFile, options)
return FindFirstNonWhitespaceColumn(lineStart, lineStart+char, sourceFile, options)
}

func findFirstNonWhitespaceColumn(startPos int, endPos int, sourceFile *ast.SourceFile, options *FormatCodeSettings) int {
func FindFirstNonWhitespaceColumn(startPos int, endPos int, sourceFile *ast.SourceFile, options *FormatCodeSettings) int {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am suspicious of this function, given it appears to return the column in terms of runes? I'm not sure it walks the chars correctly either, hmm...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what I was writing when I first went "wait, do we work in runes or in byte offsets for columns? Looks inconsistent right now". Both ways look wrong to some part of the stack around here because of that inconsistency.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think we need everything to be UTF-8 byte offsets, until they hit the LS and get converted based on the client preferences.

The only exception might be symbol baselines (UTF-16 compat?), source maps, and maybe the API, and maybe printed diagnostics? (That's a lot of exceptions...)

Related, #1578

_, col := findFirstNonWhitespaceCharacterAndColumn(startPos, endPos, sourceFile, options)
return col
}
Expand Down Expand Up @@ -249,7 +249,7 @@ func getStartLineAndCharacterForNode(n *ast.Node, sourceFile *ast.SourceFile) (l
return scanner.GetLineAndCharacterOfPosition(sourceFile, scanner.GetTokenPosOfNode(n, sourceFile, false))
}

func getContainingList(node *ast.Node, sourceFile *ast.SourceFile) *ast.NodeList {
func GetContainingList(node *ast.Node, sourceFile *ast.SourceFile) *ast.NodeList {
if node.Parent == nil {
return nil
}
Expand Down Expand Up @@ -348,7 +348,7 @@ func getVisualListRange(node *ast.Node, list core.TextRange, sourceFile *ast.Sou
}

func getContainingListOrParentStart(parent *ast.Node, child *ast.Node, sourceFile *ast.SourceFile) (line int, character int) {
containingList := getContainingList(child, sourceFile)
containingList := GetContainingList(child, sourceFile)
var startPos int
if containingList != nil {
startPos = containingList.Loc.Pos()
Expand Down
4 changes: 2 additions & 2 deletions internal/format/span.go
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,7 @@ func (w *formatSpanWorker) processChildNodes(
indentationOnListStartToken = w.indentationOnLastIndentedLine
} else {
startLinePosition := GetLineStartPositionForPosition(tokenInfo.token.Loc.Pos(), w.sourceFile)
indentationOnListStartToken = findFirstNonWhitespaceColumn(startLinePosition, tokenInfo.token.Loc.Pos(), w.sourceFile, w.formattingContext.Options)
indentationOnListStartToken = FindFirstNonWhitespaceColumn(startLinePosition, tokenInfo.token.Loc.Pos(), w.sourceFile, w.formattingContext.Options)
}

listDynamicIndentation = w.getDynamicIndentation(parent, parentStartLine, indentationOnListStartToken, w.formattingContext.Options.IndentSize)
Expand Down Expand Up @@ -578,7 +578,7 @@ func (w *formatSpanWorker) tryComputeIndentationForListItem(startPos int, endPos
} else {
startLine, _ := scanner.GetLineAndCharacterOfPosition(w.sourceFile, startPos)
startLinePosition := GetLineStartPositionForPosition(startPos, w.sourceFile)
column := findFirstNonWhitespaceColumn(startLinePosition, startPos, w.sourceFile, w.formattingContext.Options)
column := FindFirstNonWhitespaceColumn(startLinePosition, startPos, w.sourceFile, w.formattingContext.Options)
if startLine != parentStartLine || startPos == column {
// Use the base indent size if it is greater than
// the indentation of the inherited predecessor.
Expand Down
Loading
Loading