Skip to content

[DRAFT] autoimport #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

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
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 @@ -991,10 +991,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
23 changes: 23 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 @@ -2658,6 +2668,10 @@ func nodeContainsPosition(node *Node, position int) bool {
return node.Kind >= KindFirstNode && node.Pos() <= position && (position < node.End() || position == node.End() && node.Kind == KindEndOfFile)
}

func NodeRangeContainsPosition(node *Node, pos int) bool {
return node.Pos() <= pos && pos <= node.End()
}

func findImportOrRequire(text string, start int) (index int, size int) {
index = max(start, 0)
n := len(text)
Expand Down Expand Up @@ -2732,6 +2746,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 @@ -138,6 +138,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
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)
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 {
_, 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
11 changes: 0 additions & 11 deletions internal/fourslash/_scripts/failingTests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ TestCompletionEntryForArrayElementConstrainedToString2
TestCompletionEntryForClassMembers_StaticWhenBaseTypeIsNotResolved
TestCompletionEntryForUnionProperty
TestCompletionEntryForUnionProperty2
TestCompletionExportFrom
TestCompletionForComputedStringProperties
TestCompletionForMetaProperty
TestCompletionForStringLiteral
Expand Down Expand Up @@ -66,7 +65,6 @@ TestCompletionImportModuleSpecifierEndingUnsupportedExtension
TestCompletionInFunctionLikeBody_includesPrimitiveTypes
TestCompletionInJsDoc
TestCompletionInJsDocQualifiedNames
TestCompletionInNamedImportLocation
TestCompletionInUncheckedJSFile
TestCompletionJSDocNamePath
TestCompletionListBuilderLocations_VariableDeclarations
Expand Down Expand Up @@ -95,7 +93,6 @@ TestCompletionListInUnclosedTaggedTemplate02
TestCompletionListInUnclosedTemplate01
TestCompletionListInUnclosedTemplate02
TestCompletionListInvalidMemberNames
TestCompletionListInvalidMemberNames2
TestCompletionListInvalidMemberNames_escapeQuote
TestCompletionListInvalidMemberNames_startWithSpace
TestCompletionListInvalidMemberNames_withExistingIdentifier
Expand All @@ -108,12 +105,10 @@ TestCompletionListWithoutVariableinitializer
TestCompletionListsStringLiteralTypeAsIndexedAccessTypeObject
TestCompletionNoAutoInsertQuestionDotForThis
TestCompletionNoAutoInsertQuestionDotForTypeParameter
TestCompletionNoAutoInsertQuestionDotWithUserPreferencesOff
TestCompletionOfAwaitPromise1
TestCompletionOfAwaitPromise2
TestCompletionOfAwaitPromise3
TestCompletionOfAwaitPromise5
TestCompletionOfAwaitPromise6
TestCompletionOfAwaitPromise7
TestCompletionOfInterfaceAndVar
TestCompletionPreferredSuggestions1
Expand All @@ -123,7 +118,6 @@ TestCompletionsBeforeRestArg1
TestCompletionsElementAccessNumeric
TestCompletionsExportImport
TestCompletionsGenericTypeWithMultipleBases1
TestCompletionsImportOrExportSpecifier
TestCompletionsInExport
TestCompletionsInExport_moduleBlock
TestCompletionsInRequire
Expand Down Expand Up @@ -230,9 +224,6 @@ TestImportCompletions_importsMap2
TestImportCompletions_importsMap3
TestImportCompletions_importsMap4
TestImportCompletions_importsMap5
TestImportStatementCompletions4
TestImportStatementCompletions_noPatternAmbient
TestImportStatementCompletions_pnpmTransitive
TestIndexerReturnTypes1
TestIndirectClassInstantiation
TestInstanceTypesForGenericType1
Expand Down Expand Up @@ -291,7 +282,6 @@ TestLetQuickInfoAndCompletionList
TestLocalFunction
TestLocalGetReferences
TestMemberListInReopenedEnum
TestMemberListInWithBlock
TestMemberListOfExportedClass
TestMemberListOnContextualThis
TestModuleReexportedIntoGlobalQuickInfo
Expand Down Expand Up @@ -416,7 +406,6 @@ TestQuickInfoOnFunctionPropertyReturnedFromGenericFunction3
TestQuickInfoOnGenericWithConstraints1
TestQuickInfoOnInternalAliases
TestQuickInfoOnMethodOfImportEquals
TestQuickInfoOnNarrowedType
TestQuickInfoOnNarrowedTypeInModule
TestQuickInfoOnNewKeyword01
TestQuickInfoOnObjectLiteralWithAccessors
Expand Down
Loading
Loading