Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 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
7 changes: 0 additions & 7 deletions internal/fourslash/_scripts/failingTests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -168,13 +168,9 @@ TestDoubleUnderscoreCompletions
TestEditJsdocType
TestExportDefaultClass
TestExportDefaultFunction
TestFindAllReferencesDynamicImport1
TestFindAllReferencesUmdModuleAsGlobalConst
TestFindAllRefsExportEquals
TestFindAllRefsForDefaultExport03
TestFindAllRefsForModule
TestFindAllRefsModuleDotExports
TestFindAllRefs_importType_typeofImport
TestFindReferencesBindingPatternInJsdocNoCrash1
TestFindReferencesBindingPatternInJsdocNoCrash2
TestGenericCombinatorWithConstraints1
Expand Down Expand Up @@ -262,7 +258,6 @@ TestJsdocTypedefTag
TestJsdocTypedefTag2
TestJsdocTypedefTagNamespace
TestJsdocTypedefTagServices
TestJsxFindAllReferencesOnRuntimeImportWithPaths1
TestLetQuickInfoAndCompletionList
TestLocalFunction
TestMemberListInReopenedEnum
Expand Down Expand Up @@ -436,7 +431,6 @@ TestQuickinfoWrongComment
TestRecursiveInternalModuleImport
TestReferencesForStatementKeywords
TestReferencesInEmptyFile
TestReferencesIsAvailableThroughGlobalNoCrash
TestRegexDetection
TestRenameForAliasingExport02
TestRenameFromNodeModulesDep1
Expand All @@ -461,7 +455,6 @@ TestTripleSlashRefPathCompletionExtensionsAllowJSFalse
TestTripleSlashRefPathCompletionExtensionsAllowJSTrue
TestTripleSlashRefPathCompletionHiddenFile
TestTripleSlashRefPathCompletionRootdirs
TestTslibFindAllReferencesOnRuntimeImportWithPaths1
TestTsxCompletion14
TestTsxCompletion15
TestTsxCompletionNonTagLessThan
Expand Down
21 changes: 21 additions & 0 deletions internal/fourslash/tests/documentHighlightImportPath_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package fourslash_test
Copy link
Member

Choose a reason for hiding this comment

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

Does this test repro the crash?

Copy link
Member

Choose a reason for hiding this comment

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

Wondering if this still applies:

#1796 does fix it, though I have yet to make a test which actually shows the crash and not just an empty response.

Copy link
Member Author

Choose a reason for hiding this comment

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

Sort of. It returns a nil location which fails the test runner. I still could not get a good test...


import (
"testing"

"github.com/microsoft/typescript-go/internal/fourslash"
"github.com/microsoft/typescript-go/internal/testutil"
)

func TestDocumentHighlightImportPath(t *testing.T) {
t.Parallel()

defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `// @Filename: /a.ts
export const x = 0;
// @Filename: /b.ts
import { x } from "[|./a|]";`
f := fourslash.NewFourslash(t, nil /*capabilities*/, content)
f.VerifyBaselineDocumentHighlights(t, nil /*preferences*/, f.Ranges()[0])
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (

func TestFindAllReferencesDynamicImport1(t *testing.T) {
t.Parallel()
t.Skip()

defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `// @Filename: foo.ts
export function foo() { return "foo"; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (

func TestFindAllReferencesUmdModuleAsGlobalConst(t *testing.T) {
t.Parallel()
t.Skip()

defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `// @Filename: /node_modules/@types/three/three-core.d.ts
export class Vector3 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (

func TestFindAllRefsExportEquals(t *testing.T) {
t.Parallel()
t.Skip()

defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `// @Filename: /a.ts
type /*0*/T = number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (

func TestFindAllRefs_importType_typeofImport(t *testing.T) {
t.Parallel()
t.Skip()

defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `// @Filename: /a.ts
export const x = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (

func TestJsxFindAllReferencesOnRuntimeImportWithPaths1(t *testing.T) {
t.Parallel()
t.Skip()

defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `// @Filename: project/src/foo.ts
import * as x from /**/"@foo/dir/jsx-runtime";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (

func TestReferencesIsAvailableThroughGlobalNoCrash(t *testing.T) {
t.Parallel()
t.Skip()

defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `// @Filename: /packages/playwright-core/bundles/utils/node_modules/@types/debug/index.d.ts
declare var debug: debug.Debug & { debug: debug.Debug; default: debug.Debug };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (

func TestTslibFindAllReferencesOnRuntimeImportWithPaths1(t *testing.T) {
t.Parallel()
t.Skip()

defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `// @Filename: project/src/foo.ts
import * as x from /**/"tslib";
Expand Down
3 changes: 2 additions & 1 deletion internal/ls/documenthighlights.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ func (l *LanguageService) ProvideDocumentHighlights(ctx context.Context, documen
documentHighlights = l.getSyntacticDocumentHighlights(node, sourceFile)
}
// if nil is passed here we never generate an error, just pass an empty higlight
return lsproto.DocumentHighlightsOrNull{DocumentHighlights: &documentHighlights}, nil
resp := lsproto.DocumentHighlightsOrNull{DocumentHighlights: &documentHighlights}
return resp, nil
}

func (l *LanguageService) getSemanticDocumentHighlights(ctx context.Context, position int, node *ast.Node, program *compiler.Program, sourceFile *ast.SourceFile) []*lsproto.DocumentHighlight {
Expand Down
99 changes: 93 additions & 6 deletions internal/ls/findallreferences.go
Original file line number Diff line number Diff line change
Expand Up @@ -621,7 +621,7 @@ func (l *LanguageService) getReferencedSymbolsForNode(ctx context.Context, posit
}

if moduleSymbol := checker.GetMergedSymbol(resolvedRef.file.Symbol); moduleSymbol != nil {
return getReferencedSymbolsForModule(program, moduleSymbol /*excludeImportTypeOfExportEquals*/, false, sourceFiles, sourceFilesSet)
return getReferencedSymbolsForModule(ctx, program, moduleSymbol /*excludeImportTypeOfExportEquals*/, false, sourceFiles, sourceFilesSet)
}

// !!! not implemented
Expand Down Expand Up @@ -669,11 +669,11 @@ func (l *LanguageService) getReferencedSymbolsForNode(ctx context.Context, posit
}

if symbol.Name == ast.InternalSymbolNameExportEquals {
return getReferencedSymbolsForModule(program, symbol.Parent, false /*excludeImportTypeOfExportEquals*/, sourceFiles, sourceFilesSet)
return getReferencedSymbolsForModule(ctx, program, symbol.Parent, false /*excludeImportTypeOfExportEquals*/, sourceFiles, sourceFilesSet)
}

moduleReferences := l.getReferencedSymbolsForModuleIfDeclaredBySourceFile(ctx, symbol, program, sourceFiles, checker, options, sourceFilesSet) // !!! cancellationToken
if moduleReferences != nil && symbol.Flags&ast.SymbolFlagsTransient != 0 {
if moduleReferences != nil && symbol.Flags&ast.SymbolFlagsTransient == 0 {
return moduleReferences
}

Expand All @@ -696,7 +696,7 @@ func (l *LanguageService) getReferencedSymbolsForModuleIfDeclaredBySourceFile(ct
}
exportEquals := symbol.Exports[ast.InternalSymbolNameExportEquals]
// If exportEquals != nil, we're about to add references to `import("mod")` anyway, so don't double-count them.
moduleReferences := getReferencedSymbolsForModule(program, symbol, exportEquals != nil, sourceFiles, sourceFilesSet)
moduleReferences := getReferencedSymbolsForModule(ctx, program, symbol, exportEquals != nil, sourceFiles, sourceFilesSet)
if exportEquals == nil || !sourceFilesSet.Has(moduleSourceFileName) {
return moduleReferences
}
Expand Down Expand Up @@ -975,8 +975,95 @@ func getMergedAliasedSymbolOfNamespaceExportDeclaration(node *ast.Node, symbol *
return nil
}

func getReferencedSymbolsForModule(program *compiler.Program, symbol *ast.Symbol, excludeImportTypeOfExportEquals bool, sourceFiles []*ast.SourceFile, sourceFilesSet *collections.Set[string]) []*SymbolAndEntries {
// !!! not implemented
func getReferencedSymbolsForModule(ctx context.Context, program *compiler.Program, symbol *ast.Symbol, excludeImportTypeOfExportEquals bool, sourceFiles []*ast.SourceFile, sourceFilesSet *collections.Set[string]) []*SymbolAndEntries {
debug.Assert(symbol.ValueDeclaration != nil)

checker, done := program.GetTypeChecker(ctx)
defer done()

moduleRefs := findModuleReferences(program, sourceFiles, symbol, checker)
references := core.MapNonNil(moduleRefs, func(reference ModuleReference) *referenceEntry {
switch reference.kind {
case ModuleReferenceKindImport:
parent := reference.literal.Parent
if ast.IsLiteralTypeNode(parent) {
importType := parent.Parent
if ast.IsImportTypeNode(importType) {
importTypeNode := importType.AsImportTypeNode()
if excludeImportTypeOfExportEquals && importTypeNode.Qualifier == nil {
return nil
}
}
}
// import("foo") with no qualifier will reference the `export =` of the module, which may be referenced anyway.
return newNodeEntry(reference.literal)
case ModuleReferenceKindImplicit:
// For implicit references (e.g., JSX runtime imports), return the first statement or the whole file.
// We skip the complex JSX detection for now and just use the first statement or the file itself.
var rangeNode *ast.Node
if reference.referencingFile.Statements != nil && len(reference.referencingFile.Statements.Nodes) > 0 {
rangeNode = reference.referencingFile.Statements.Nodes[0]
} else {
rangeNode = reference.referencingFile.AsNode()
}
return newNodeEntry(rangeNode)
case ModuleReferenceKindReference:
// <reference path> or <reference types>
// We can't easily create a proper range entry here without access to LanguageService,
// but we can create a node-based entry pointing to the source file which will be resolved later
return newNodeEntry(reference.referencingFile.AsNode())
}
return nil
})

// Add references to the module declarations themselves
if len(symbol.Declarations) > 0 {
for _, decl := range symbol.Declarations {
switch decl.Kind {
case ast.KindSourceFile:
// Don't include the source file itself. (This may not be ideal behavior, but awkward to include an entire file as a reference.)
continue
case ast.KindModuleDeclaration:
if sourceFilesSet.Has(ast.GetSourceFileOfNode(decl).FileName()) {
references = append(references, newNodeEntry(decl.AsModuleDeclaration().Name()))
}
default:
// This may be merged with something.
debug.Assert(symbol.Flags&ast.SymbolFlagsTransient != 0, "Expected a module symbol to be declared by a SourceFile or ModuleDeclaration.")
}
}
}

// Handle export equals declarations
exported := symbol.Exports[ast.InternalSymbolNameExportEquals]
if exported != nil && len(exported.Declarations) > 0 {
for _, decl := range exported.Declarations {
sourceFile := ast.GetSourceFileOfNode(decl)
if sourceFilesSet.Has(sourceFile.FileName()) {
var node *ast.Node
// At `module.exports = ...`, reference node is `module`
if ast.IsBinaryExpression(decl) && ast.IsPropertyAccessExpression(decl.AsBinaryExpression().Left) {
node = decl.AsBinaryExpression().Left.AsPropertyAccessExpression().Expression
} else if ast.IsExportAssignment(decl) {
// Find the export keyword - for now, just use the declaration itself
node = decl
} else {
node = ast.GetNameOfDeclaration(decl)
if node == nil {
node = decl
}
}
references = append(references, newNodeEntry(node))
}
}
}

if len(references) > 0 {
return []*SymbolAndEntries{{
definition: &Definition{Kind: definitionKindSymbol, symbol: symbol},
references: references,
}}
}
return nil
}

Expand Down
72 changes: 72 additions & 0 deletions internal/ls/importTracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/checker"
"github.com/microsoft/typescript-go/internal/collections"
"github.com/microsoft/typescript-go/internal/compiler"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/debug"
)
Expand Down Expand Up @@ -42,6 +43,22 @@ type ImportsResult struct {

type ImportTracker func(exportSymbol *ast.Symbol, exportInfo *ExportInfo, isForRename bool) *ImportsResult

type ModuleReferenceKind int32

const (
ModuleReferenceKindImport ModuleReferenceKind = iota
ModuleReferenceKindReference
ModuleReferenceKindImplicit
)

// ModuleReference represents a reference to a module, either via import, <reference>, or implicit reference
type ModuleReference struct {
kind ModuleReferenceKind
literal *ast.Node // for import and implicit kinds (StringLiteralLike)
referencingFile *ast.SourceFile
ref *ast.FileReference // for reference kind
}

// Creates the imports map and returns an ImportTracker that uses it. Call this lazily to avoid calling `getDirectImportsMap` unnecessarily.
func createImportTracker(sourceFiles []*ast.SourceFile, sourceFilesSet *collections.Set[string], checker *checker.Checker) ImportTracker {
allDirectImports := getDirectImportsMap(sourceFiles, checker)
Expand Down Expand Up @@ -654,3 +671,58 @@ func symbolNameNoDefault(symbol *ast.Symbol) string {
}
return ""
}

// findModuleReferences finds all references to a module symbol across the given source files.
// This includes import statements, <reference> directives, and implicit references (e.g., JSX runtime imports).
func findModuleReferences(program *compiler.Program, sourceFiles []*ast.SourceFile, searchModuleSymbol *ast.Symbol, checker *checker.Checker) []ModuleReference {
refs := []ModuleReference{}

for _, referencingFile := range sourceFiles {
searchSourceFile := searchModuleSymbol.ValueDeclaration
if searchSourceFile != nil && searchSourceFile.Kind == ast.KindSourceFile {
// Check <reference path> directives
for _, ref := range referencingFile.ReferencedFiles {
if program.GetSourceFileFromReference(referencingFile, ref) == searchSourceFile.AsSourceFile() {
refs = append(refs, ModuleReference{
kind: ModuleReferenceKindReference,
referencingFile: referencingFile,
ref: ref,
})
}
}

// Check <reference types> directives
for _, ref := range referencingFile.TypeReferenceDirectives {
referenced := program.GetResolvedTypeReferenceDirectiveFromTypeReferenceDirective(ref, referencingFile)
if referenced != nil && referenced.ResolvedFileName == searchSourceFile.AsSourceFile().FileName() {
refs = append(refs, ModuleReference{
kind: ModuleReferenceKindReference,
referencingFile: referencingFile,
ref: ref,
})
}
}
}

// Check all imports (including require() calls)
forEachImport(referencingFile, func(importDecl *ast.Node, moduleSpecifier *ast.Node) {
moduleSymbol := checker.GetSymbolAtLocation(moduleSpecifier)
if moduleSymbol == searchModuleSymbol {
if ast.NodeIsSynthesized(importDecl) {
refs = append(refs, ModuleReference{
kind: ModuleReferenceKindImplicit,
literal: moduleSpecifier,
referencingFile: referencingFile,
})
} else {
refs = append(refs, ModuleReference{
kind: ModuleReferenceKindImport,
literal: moduleSpecifier,
})
}
}
})
}

return refs
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// === documentHighlights ===
// === /b.ts ===
// import { x } from "/*HIGHLIGHTS*/[|./a|]";
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// === documentHighlights ===
// === /b.ts ===
// import { x } from "/*HIGHLIGHTS*/[|./a|]";



// === documentHighlights ===
// === /c/sub.js ===
// const a = require("/*HIGHLIGHTS*/[|../a|]");
Loading
Loading