Skip to content

Module specifier completions - #2587

Merged
gabritto merged 22 commits into
mainfrom
gabritto/module_completions
Jan 28, 2026
Merged

Module specifier completions#2587
gabritto merged 22 commits into
mainfrom
gabritto/module_completions

Conversation

@gabritto

@gabritto gabritto commented Jan 26, 2026

Copy link
Copy Markdown
Member

Fixes #2383

I opted to port the code as-is for now because it wasn't very clear to me how to modify the existing code to use the autoimports registry for non-relative specifier completions. In the future we might want to do this for performance or correctness reasons. It would likely require untangling the existing code, which ends up calling the same function for getCompletionEntriesForDirectoryFragment and reading from the file system for both relative and non-relative imports. We'd probably also have to simplify how we collect module specifier candidates to make better use of the information in the registry, e.g. if a user typed "packageFoo/bar/baz/f", right now we end up reading the directory packageFoo/bar/baz and computing completions for that path, but we could just use all of the module specifier information collected for packageFoo and rely on the client to do the filtering for us for entries that match "packageFoo/bar/baz/f" (or do the filtering ourselves after we collect the entries).

Note:
We bypass the snapshot's immutable view of files to be able to see what files and directories exist. This means there could be inconsistencies between the module specifier completions and the state of the current snapshot, but the worst that could happen here is extra or missing module specifiers in completions, so that seems fine.

Future work:

  • This PR currently uses the existing ReadDirectory as ported from Strada that uses regexes. We'll need Rewrite matchFiles to not use regexp2, remove other regexp uses #2407 to get rid of regex use. The usage of ReadDirectory by module specifier completions probably doesn't need full fidelity with the original Strada code, the only nice to haves I can think of are the filtering of *.min.js files and maybe of paths starting with ..

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR implements module specifier completions for the language server, fixing issue #2383 where path suggestions were missing in import/export statements. The implementation adds support for relative paths, non-relative modules, package.json exports/imports, typings, and triple-slash reference directives.

Changes:

  • Added comprehensive module specifier completion logic (~1300 lines) including support for relative paths, node_modules, package.json exports/imports, path mappings, and triple-slash references
  • Bypassed snapshot immutability to access real-time filesystem state for completion suggestions
  • Updated test infrastructure to properly handle empty implicit first files in fourslash tests
  • Fixed numerous test expectations and moved previously failing tests to passing status

Reviewed changes

Copilot reviewed 39 out of 39 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
internal/ls/string_completions.go Core completion logic with support for module specifiers, path mappings, package.json, and triple-slash references (~1300 new lines)
internal/ls/languageservice.go Added DirectoryExists, ReadDirectory, and GetDirectories methods for completions
internal/ls/host.go Extended Host interface with filesystem query methods that bypass snapshot immutability
internal/project/snapshot.go Added DirectoryExists, GetDirectories, and ReadDirectory methods delegating to underlying filesystem
internal/ls/completions.go Removed file extension detail logic from completion item creation
internal/tspath/extension.go Added GetPossibleOriginalInputExtensionForExtension utility function
internal/module/resolver.go Moved extension utility to tspath package and improved directory separator handling
internal/testrunner/test_case_parser.go Fixed logic to properly handle empty implicit first files in fourslash tests
internal/astnav/tokens.go Fixed isValidPrecedingNode to check JSDoc for EndOfFile tokens
internal/fourslash/_scripts/*.txt Updated failing/manual test lists reflecting newly passing tests
internal/fourslash/tests/manual/*.go Updated test expectations and file paths to match new completion behavior
internal/fourslash/tests/gen/*.go Deleted tests for path completion features that now work correctly
internal/fourslash/fourslash.go Added t.Helper() calls and improved nil completion handling

Comment thread internal/ls/string_completions.go Outdated
Comment thread internal/ls/string_completions.go Outdated
Comment thread internal/ls/string_completions.go Outdated
Comment thread internal/project/snapshot.go Outdated
Comment thread internal/ls/host.go
Comment thread internal/ls/string_completions.go Outdated
importNameCodeFixDefaultExport7
importNameCodeFixOptionalImport0
formattingFatArrowFunctions No newline at end of file
autoCloseFragment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This file was in a bad state so I had to dedupe + sort it again.

Comment thread internal/astnav/tokens.go
}

func isValidPrecedingNode(node *ast.Node, sourceFile *ast.SourceFile) bool {
if node.Kind == ast.KindEndOfFile {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This matches a special case we already had in Strada and fixes a crash in one of the tests.

if kindModifiers.Has(lsutil.ScriptElementKindModifierDeprecated) {
tags = &[]lsproto.CompletionItemTag{lsproto.CompletionItemTagDeprecated}
}
if kind == lsproto.CompletionItemKindFile {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Moved this logic to string_completions.go.

if isPattern {
combinedLookup = strings.ReplaceAll(targetString, "*", subpath)
}
scopeContainingDirectory := tspath.EnsureTrailingDirectorySeparator(scope.PackageDirectory)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This fixes a crash when scopeContainingDirectory is /.

if currentFileName != "" {
// Store result file - always save for regular tests, but skip empty implicit first file for fourslash
shouldSaveFile := currentFileContent.Len() != 0 || !options.AllowImplicitFirstFile
shouldSaveFile := !options.AllowImplicitFirstFile || currentFileContent.Len() != 0 || hasSeenFile

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This allows us to parse empty files in tests, e.g.:

// @Filename: foo.ts
// @Filename: bar.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 39 out of 39 changed files in this pull request and generated 3 comments.

Comment thread internal/ls/string_completions.go Outdated
Comment thread internal/project/snapshot.go Outdated
Comment thread internal/fourslash/_scripts/manualTests.txt Outdated
@gabritto
gabritto marked this pull request as ready for review January 27, 2026 21:54
@gabritto
gabritto requested a review from andrewbranch January 27, 2026 21:54
@gabritto
gabritto added this pull request to the merge queue Jan 28, 2026
Merged via the queue into main with commit 9541870 Jan 28, 2026
21 checks passed
@gabritto
gabritto deleted the gabritto/module_completions branch January 28, 2026 19:17
Copilot AI pushed a commit that referenced this pull request Feb 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

LSP: Path suggestion missing (import/export)

3 participants