Skip to content
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
78 changes: 78 additions & 0 deletions internal/fourslash/tests/autoImportFileExcludePatterns_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package fourslash_test

import (
"testing"

"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/fourslash"
. "github.com/microsoft/typescript-go/internal/fourslash/tests/util"
"github.com/microsoft/typescript-go/internal/ls"
"github.com/microsoft/typescript-go/internal/testutil"
)

func TestAutoImportFileExcludePatterns1(t *testing.T) {
t.Parallel()
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `// @Filename: /project/a.ts
export const varA = 10;

// @Filename: /project/excluded/b.ts
export const varB = 20;

// @Filename: /project/c.ts
varA/**/
`
f := fourslash.NewFourslash(t, nil /*capabilities*/, content)

// With exclusion pattern - only varA from ./a should be available as auto-import
f.VerifyCompletions(t, "", &fourslash.CompletionsExpectedList{
UserPreferences: &ls.UserPreferences{
IncludeCompletionsForModuleExports: core.TSTrue,
IncludeCompletionsForImportStatements: core.TSTrue,
AutoImportFileExcludePatterns: []string{"/project/excluded/**/*"},
},
ItemDefaults: &fourslash.CompletionsExpectedItemDefaults{
CommitCharacters: &DefaultCommitCharacters,
EditRange: Ignored,
},
Items: &fourslash.CompletionsExpectedItems{
Includes: []fourslash.CompletionsExpectedItem{"varA"},
Excludes: []string{"varB"},
},
})
}

func TestAutoImportFileExcludePatterns2(t *testing.T) {
t.Parallel()
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `// @Filename: /project/src/a.ts
export const varA = 10;

// @Filename: /project/tests/b.ts
export const varB = 20;

// @Filename: /project/node_modules/c/index.ts
export const varC = 30;

// @Filename: /project/src/main.ts
varA/**/
`
f := fourslash.NewFourslash(t, nil /*capabilities*/, content)

// Exclude tests and node_modules directories
f.VerifyCompletions(t, "", &fourslash.CompletionsExpectedList{
UserPreferences: &ls.UserPreferences{
IncludeCompletionsForModuleExports: core.TSTrue,
IncludeCompletionsForImportStatements: core.TSTrue,
AutoImportFileExcludePatterns: []string{"/project/tests/**/*", "/project/node_modules/**/*"},
},
ItemDefaults: &fourslash.CompletionsExpectedItemDefaults{
CommitCharacters: &DefaultCommitCharacters,
EditRange: Ignored,
},
Items: &fourslash.CompletionsExpectedItems{
Includes: []fourslash.CompletionsExpectedItem{"varA"},
Excludes: []string{"varB", "varC"},
},
})
}
83 changes: 74 additions & 9 deletions internal/ls/autoimports.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"strings"

"github.com/dlclark/regexp2"
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/astnav"
"github.com/microsoft/typescript-go/internal/binder"
Expand All @@ -19,6 +20,7 @@ import (
"github.com/microsoft/typescript-go/internal/modulespecifiers"
"github.com/microsoft/typescript-go/internal/stringutil"
"github.com/microsoft/typescript-go/internal/tspath"
"github.com/microsoft/typescript-go/internal/vfs"
)

type SymbolExportInfo struct {
Expand Down Expand Up @@ -1328,20 +1330,69 @@ func getDefaultLikeExportNameFromDeclaration(symbol *ast.Symbol) string {
return ""
}

// getIsExcludedPatterns converts autoImportFileExcludePatterns to regexes
func getIsExcludedPatterns(preferences *UserPreferences, useCaseSensitiveFileNames bool) []*regexp2.Regexp {
if len(preferences.AutoImportFileExcludePatterns) == 0 {
return nil
}

patterns := make([]*regexp2.Regexp, 0, len(preferences.AutoImportFileExcludePatterns))
for _, spec := range preferences.AutoImportFileExcludePatterns {
// The client is expected to send rooted path specs since we don't know
// what directory a relative path is relative to.
pattern := vfs.GetPatternFromSpec(spec, "", "exclude")
if pattern != "" {
patterns = append(patterns, vfs.GetRegexFromPattern(pattern, useCaseSensitiveFileNames))
}
}
return patterns
}

// getIsExcluded returns a function that checks if a source file is excluded by the patterns
func getIsExcluded(excludePatterns []*regexp2.Regexp) func(*ast.SourceFile) bool {
if len(excludePatterns) == 0 {
return func(*ast.SourceFile) bool { return false }
}

return func(sourceFile *ast.SourceFile) bool {
fileName := sourceFile.FileName()
for _, pattern := range excludePatterns {
match, err := pattern.MatchString(fileName)
if err == nil && match {
return true
}
}
// Note: TypeScript also checks for symlinks, but we're simplifying for now
// as the Go implementation doesn't appear to have symlink cache support yet
return false
}
}

// getIsFileExcluded returns a function that checks if a source file should be excluded from auto-imports
func getIsFileExcluded(host Host, preferences *UserPreferences) func(*ast.SourceFile) bool {
if len(preferences.AutoImportFileExcludePatterns) == 0 {
return func(*ast.SourceFile) bool { return false }
}
return getIsExcluded(getIsExcludedPatterns(preferences, host.UseCaseSensitiveFileNames()))
}

func forEachExternalModuleToImportFrom(
ch *checker.Checker,
program *compiler.Program,
preferences *UserPreferences,
useCaseSensitiveFileNames bool,
// useAutoImportProvider bool,
cb func(module *ast.Symbol, moduleFile *ast.SourceFile, checker *checker.Checker, isFromPackageJson bool),
) {
// !!! excludePatterns
// excludePatterns := preferences.autoImportFileExcludePatterns && getIsExcludedPatterns(preferences, useCaseSensitiveFileNames)
var excludePatterns []*regexp2.Regexp
if len(preferences.AutoImportFileExcludePatterns) > 0 {
excludePatterns = getIsExcludedPatterns(preferences, useCaseSensitiveFileNames)
}

forEachExternalModule(
ch,
program.GetSourceFiles(),
// !!! excludePatterns,
excludePatterns,
func(module *ast.Symbol, file *ast.SourceFile) {
cb(module, file, ch, false)
},
Expand Down Expand Up @@ -1370,19 +1421,33 @@ func forEachExternalModuleToImportFrom(
func forEachExternalModule(
ch *checker.Checker,
allSourceFiles []*ast.SourceFile,
// excludePatterns []RegExp,
excludePatterns []*regexp2.Regexp,
cb func(moduleSymbol *ast.Symbol, sourceFile *ast.SourceFile),
) {
// !!! excludePatterns
// isExcluded := excludePatterns && getIsExcluded(excludePatterns, host)
isExcluded := getIsExcluded(excludePatterns)

for _, ambient := range ch.GetAmbientModules() {
if !strings.Contains(ambient.Name, "*") /* && !(excludePatterns && ambient.Declarations.every(func (d){ return isExcluded(d.getSourceFile())})) */ {
cb(ambient, nil /*sourceFile*/)
if !strings.Contains(ambient.Name, "*") {
// Check if all declarations are excluded
allExcluded := true
if excludePatterns != nil && ambient.Declarations != nil {
for _, d := range ambient.Declarations {
if sf := ast.GetSourceFileOfNode(d); sf != nil && !isExcluded(sf) {
allExcluded = false
break
}
}
} else {
allExcluded = false
}

if !allExcluded {
cb(ambient, nil /*sourceFile*/)
}
}
}
for _, sourceFile := range allSourceFiles {
if ast.IsExternalOrCommonJSModule(sourceFile) /* && !isExcluded(sourceFile) */ {
if ast.IsExternalOrCommonJSModule(sourceFile) && !isExcluded(sourceFile) {
cb(ch.GetMergedSymbol(sourceFile.Symbol), sourceFile)
}
}
Expand Down
2 changes: 2 additions & 0 deletions internal/ls/autoimportsexportinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func (l *LanguageService) getExportInfos(
ch,
l.GetProgram(),
preferences,
l.host.UseCaseSensitiveFileNames(),
// /*useAutoImportProvider*/ true,
func(moduleSymbol *ast.Symbol, moduleFile *ast.SourceFile, ch *checker.Checker, isFromPackageJson bool) {
if moduleCount = moduleCount + 1; moduleCount%100 == 0 && ctx.Err() != nil {
Expand Down Expand Up @@ -125,6 +126,7 @@ func (l *LanguageService) searchExportInfosForCompletions(
ch,
l.GetProgram(),
preferences,
l.host.UseCaseSensitiveFileNames(),
// /*useAutoImportProvider*/ true,
func(moduleSymbol *ast.Symbol, moduleFile *ast.SourceFile, ch *checker.Checker, isFromPackageJson bool) {
if moduleCount = moduleCount + 1; moduleCount%100 == 0 && ctx.Err() != nil {
Expand Down
3 changes: 1 addition & 2 deletions internal/ls/findallreferences.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import (
"github.com/microsoft/typescript-go/internal/compiler"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/debug"
"github.com/microsoft/typescript-go/internal/ls"
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
"github.com/microsoft/typescript-go/internal/scanner"
"github.com/microsoft/typescript-go/internal/stringutil"
Expand Down Expand Up @@ -453,7 +452,7 @@ func (l *LanguageService) getImplementationReferenceEntries(ctx context.Context,
return core.FlatMap(symbolsAndEntries, func(s *SymbolAndEntries) []*referenceEntry { return s.references })
}

func (l *LanguageService) ProvideRename(ctx context.Context, params *lsproto.RenameParams, prefs *ls.UserPreferences) (lsproto.WorkspaceEditOrNull, error) {
func (l *LanguageService) ProvideRename(ctx context.Context, params *lsproto.RenameParams, prefs *UserPreferences) (lsproto.WorkspaceEditOrNull, error) {
program, sourceFile := l.getProgramAndFile(params.TextDocument.Uri)
position := int(l.converters.LineAndCharacterToPosition(sourceFile, params.Position))
node := astnav.GetTouchingPropertyName(sourceFile, position)
Expand Down