Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
b603e65
Implement yarn pnp api
GGomez99 Aug 8, 2025
a68a977
Create zipvfs + tests
GGomez99 Aug 8, 2025
7ab707a
Wrap all vfs to handle zip
GGomez99 Aug 8, 2025
faea6f7
Temporarly insert virtual path handling in zipvfs
GGomez99 Aug 12, 2025
d42bf23
Insert pnp in most important resolvers
GGomez99 Aug 12, 2025
ccb4524
Add some todos for potential unimplemented places
GGomez99 Aug 12, 2025
c505d83
Add basic caching for zipvfs
GGomez99 Aug 13, 2025
e6f86e8
Improve data structure for FindLocator
GGomez99 Aug 13, 2025
17d4e68
Parse pnp data from .pnp.cjs
GGomez99 Aug 14, 2025
aa12649
Use pointers for GetPackage and implement WIP GetResolvedProjectRefer…
GGomez99 Aug 18, 2025
1e204b1
Generate zip URIs for Go to definition
GGomez99 Aug 19, 2025
9eafbd4
Use pnp api in readPackageJsonPeerDependencies
GGomez99 Aug 19, 2025
3129404
Enable typescript on zip files in IDE
GGomez99 Aug 19, 2025
1c44670
Rephrase todo comment on caching
GGomez99 Aug 19, 2025
cd3fa19
Fix type roots not working depending on where tsc is run
GGomez99 Aug 21, 2025
53c7edc
Don't log anything when the pnp api is not available
GGomez99 Aug 21, 2025
545e075
Merge remote-tracking branch 'upstream/main' into guyllian.gomez/yarn…
arcanis Sep 5, 2025
7a0bf26
Merge pull request #2 from arcanis/guyllian.gomez/yarn-pnp-support
GGomez99 Sep 10, 2025
94b4f5d
Add comment on pnp api spec
GGomez99 Sep 11, 2025
0045eac
Use zipfs only when pnp api is available
GGomez99 Sep 11, 2025
6cbb866
Extract tryGetModuleNameFromPnp
GGomez99 Sep 11, 2025
7db373f
Mark files from __virtual__ folders as ExternalLibraryImport
GGomez99 Sep 12, 2025
512f118
Make go to definition work in zip files + add test case
GGomez99 Sep 15, 2025
c7f1846
Only display importable symbols on completion from pnpapi
GGomez99 Sep 15, 2025
a8e777e
Fix import suggestion and autocomplete with yarn pnp
GGomez99 Sep 17, 2025
cc09cad
Remove fonction that has been moved after rebase
GGomez99 Sep 17, 2025
9c27cbe
Make GetEffectiveTypeRoots use the correct baseDir for pnp resolution
GGomez99 Sep 22, 2025
4e844d1
Fix parsing for pnpExclusionList
GGomez99 Sep 22, 2025
e2bbb8b
Rename zipvfs to pnpvfs
GGomez99 Sep 22, 2025
2bd397f
Always return true for pnpvfs.UseCaseSensitiveFileNames()
GGomez99 Sep 22, 2025
43354f3
Merge branch 'main' into guyllian.gomez/yarn-pnp-support
GGomez99 Oct 3, 2025
8d73cd4
Merge branch 'main' into guyllian.gomez/yarn-pnp-support
GGomez99 Oct 6, 2025
a96aab5
Improve pnpvfs init
GGomez99 Oct 8, 2025
227839e
Extract pnp type roots appending
GGomez99 Oct 8, 2025
bd256ad
Add comments on pnp helpers and create a IsExternalLibraryImport helper
GGomez99 Oct 8, 2025
b589ce0
Merge branch 'main' into guyllian.gomez/yarn-pnp-support
GGomez99 Oct 8, 2025
df10a92
Refine zipvfs tests and fix issue with GetAccessibleEntries
GGomez99 Oct 9, 2025
fe6e4a5
Initialise the FS at the session level for LSP
GGomez99 Oct 9, 2025
0b8d7da
Add tests for virtual paths and panic on fs.WriteFile
GGomez99 Oct 9, 2025
be2aad8
Fix current unit tests
GGomez99 Oct 10, 2025
90bd2e0
Make a pnp cache compatible with tests
GGomez99 Oct 14, 2025
8cbd23d
Add simple pnp test
GGomez99 Oct 14, 2025
5588157
Simplify caching and use permanent cache for pnpvfs
GGomez99 Oct 17, 2025
da6b622
Merge branch 'main' into guyllian.gomez/yarn-pnp-support
GGomez99 Oct 21, 2025
c13f422
Fix FindLocator implementation
GGomez99 Oct 21, 2025
a6c6db8
Add more pnp resolver tests
GGomez99 Oct 23, 2025
cf02630
Add declaration emit test for pnp
GGomez99 Oct 23, 2025
05e27f1
Remove usage of filepath
GGomez99 Oct 23, 2025
9872c4e
Error handling for wrong regexes of ignorePatternData in pnp manifest
GGomez99 Oct 24, 2025
f8e8d8a
Fix pnpVfs with usages of tspath.CombinePaths
GGomez99 Oct 24, 2025
b98ce24
Remove usages of os, abstract the provided fs and init PnP API separa…
GGomez99 Oct 24, 2025
ded9d9f
Cleanup code and update comments
GGomez99 Oct 24, 2025
59ab5e6
Merge branch 'main' into guyllian.gomez/yarn-pnp-support
GGomez99 Oct 24, 2025
18c94aa
Apply lint fixes
GGomez99 Oct 24, 2025
4551f2b
Remove unnecessary comment
GGomez99 Oct 27, 2025
4f10fb8
Merge branch 'main' into guyllian.gomez/yarn-pnp-support
GGomez99 Oct 27, 2025
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
9 changes: 8 additions & 1 deletion _extension/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,14 @@ export class Client {
this.clientOptions = {
documentSelector: [
...jsTsLanguageModes.map(language => ({ scheme: "file", language })),
...jsTsLanguageModes.map(language => ({ scheme: "untitled", language })),
...jsTsLanguageModes.map(language => ({
scheme: "untitled",
language,
})),
...jsTsLanguageModes.map(language => ({
scheme: "zip",
language,
})),
],
outputChannel: this.outputChannel,
traceOutputChannel: this.traceOutputChannel,
Expand Down
11 changes: 10 additions & 1 deletion cmd/tsgo/sys.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import (

"github.com/microsoft/typescript-go/internal/bundled"
"github.com/microsoft/typescript-go/internal/execute/tsc"
"github.com/microsoft/typescript-go/internal/pnp"
"github.com/microsoft/typescript-go/internal/tspath"
"github.com/microsoft/typescript-go/internal/vfs"
"github.com/microsoft/typescript-go/internal/vfs/osvfs"
"github.com/microsoft/typescript-go/internal/vfs/pnpvfs"
"golang.org/x/term"
)

Expand Down Expand Up @@ -66,9 +68,16 @@ func newSystem() *osSys {
os.Exit(int(tsc.ExitStatusInvalidProject_OutputsSkipped))
}

var fs vfs.FS = osvfs.FS()

pnpApi := pnp.InitPnpApi(fs, tspath.NormalizePath(cwd))
if pnpApi != nil {
fs = pnpvfs.From(fs)
}

return &osSys{
cwd: tspath.NormalizePath(cwd),
fs: bundled.WrapFS(osvfs.FS()),
fs: bundled.WrapFS(fs),
defaultLibraryPath: bundled.LibPath(),
writer: os.Stdout,
start: time.Now(),
Expand Down
11 changes: 10 additions & 1 deletion internal/api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ import (
"github.com/microsoft/typescript-go/internal/bundled"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
"github.com/microsoft/typescript-go/internal/pnp"
"github.com/microsoft/typescript-go/internal/project"
"github.com/microsoft/typescript-go/internal/project/logging"
"github.com/microsoft/typescript-go/internal/vfs"
"github.com/microsoft/typescript-go/internal/vfs/osvfs"
"github.com/microsoft/typescript-go/internal/vfs/pnpvfs"
)

//go:generate go tool golang.org/x/tools/cmd/stringer -type=MessageType -output=stringer_generated.go
Expand Down Expand Up @@ -93,12 +95,19 @@ func NewServer(options *ServerOptions) *Server {
panic("Cwd is required")
}

var fs vfs.FS = osvfs.FS()

pnpApi := pnp.InitPnpApi(fs, options.Cwd)
if pnpApi != nil {
fs = pnpvfs.From(fs)
}

server := &Server{
r: bufio.NewReader(options.In),
w: bufio.NewWriter(options.Out),
stderr: options.Err,
cwd: options.Cwd,
fs: bundled.WrapFS(osvfs.FS()),
fs: bundled.WrapFS(fs),
defaultLibraryPath: options.DefaultLibraryPath,
}
logger := logging.NewLogger(options.Err)
Expand Down
9 changes: 9 additions & 0 deletions internal/core/compileroptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"sync"

"github.com/microsoft/typescript-go/internal/collections"
"github.com/microsoft/typescript-go/internal/pnp"
"github.com/microsoft/typescript-go/internal/tspath"
)

Expand Down Expand Up @@ -322,6 +323,14 @@ func (options *CompilerOptions) GetEffectiveTypeRoots(currentDirectory string) (
}
}

nmTypes, nmFromConfig := options.GetNodeModulesTypeRoots(baseDir)

typeRoots, nmFromConfig := pnp.AppendPnpTypeRoots(nmTypes, baseDir, nmFromConfig)

return typeRoots, nmFromConfig
}

func (options *CompilerOptions) GetNodeModulesTypeRoots(baseDir string) (result []string, fromConfig bool) {
typeRoots := make([]string, 0, strings.Count(baseDir, "/"))
tspath.ForEachAncestorDirectory(baseDir, func(dir string) (any, bool) {
typeRoots = append(typeRoots, tspath.CombinePaths(dir, "node_modules", "@types"))
Expand Down
6 changes: 6 additions & 0 deletions internal/ls/autoimports.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/microsoft/typescript-go/internal/module"
"github.com/microsoft/typescript-go/internal/modulespecifiers"
"github.com/microsoft/typescript-go/internal/packagejson"
"github.com/microsoft/typescript-go/internal/pnp"
"github.com/microsoft/typescript-go/internal/stringutil"
"github.com/microsoft/typescript-go/internal/tspath"
)
Expand Down Expand Up @@ -418,6 +419,11 @@ func (l *LanguageService) isImportable(
// }

fromPath := fromFile.FileName()
pnpApi := pnp.GetPnpApi(fromPath)
if pnpApi != nil {
return pnpApi.IsImportable(fromPath, toFile.FileName())
}

useCaseSensitiveFileNames := moduleSpecifierResolutionHost.UseCaseSensitiveFileNames()
globalTypingsCache := l.GetProgram().GetGlobalTypingsCacheLocation()
modulePaths := modulespecifiers.GetEachFileNameOfModule(
Expand Down
9 changes: 8 additions & 1 deletion internal/ls/converters.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,14 @@ func FileNameToDocumentURI(fileName string) lsproto.DocumentUri {
parts[i] = extraEscapeReplacer.Replace(url.PathEscape(part))
}

return lsproto.DocumentUri("file://" + volume + strings.Join(parts, "/"))
var prefix string
if tspath.IsZipPath(fileName) {
prefix = "zip:"
} else {
prefix = "file:"
}

return lsproto.DocumentUri(prefix + "//" + volume + strings.Join(parts, "/"))
}

func (c *Converters) LineAndCharacterToPosition(script Script, lineAndCharacter lsproto.Position) core.TextPos {
Expand Down
6 changes: 6 additions & 0 deletions internal/ls/converters_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ func TestDocumentURIToFileName(t *testing.T) {
{"file://localhost/c%24/GitDevelopment/express", "//localhost/c$/GitDevelopment/express"},
{"file:///c%3A/test%20with%20%2525/c%23code", "c:/test with %25/c#code"},

{"zip:///path/to/archive.zip/file.ts", "/path/to/archive.zip/file.ts"},
{"zip:///d:/work/tsgo932/lib/archive.zip/utils.ts", "d:/work/tsgo932/lib/archive.zip/utils.ts"},

{"untitled:Untitled-1", "^/untitled/ts-nul-authority/Untitled-1"},
{"untitled:Untitled-1#fragment", "^/untitled/ts-nul-authority/Untitled-1#fragment"},
{"untitled:c:/Users/jrieken/Code/abc.txt", "^/untitled/ts-nul-authority/c:/Users/jrieken/Code/abc.txt"},
Expand Down Expand Up @@ -69,6 +72,9 @@ func TestFileNameToDocumentURI(t *testing.T) {
{"//localhost/c$/GitDevelopment/express", "file://localhost/c%24/GitDevelopment/express"},
{"c:/test with %25/c#code", "file:///c%3A/test%20with%20%2525/c%23code"},

{"/path/to/archive.zip/file.ts", "zip:///path/to/archive.zip/file.ts"},
{"d:/work/tsgo932/lib/archive.zip/utils.ts", "zip:///d%3A/work/tsgo932/lib/archive.zip/utils.ts"},

{"^/untitled/ts-nul-authority/Untitled-1", "untitled:Untitled-1"},
{"^/untitled/ts-nul-authority/c:/Users/jrieken/Code/abc.txt", "untitled:c:/Users/jrieken/Code/abc.txt"},
{"^/untitled/ts-nul-authority///wsl%2Bubuntu/home/jabaile/work/TypeScript-go/newfile.ts", "untitled://wsl%2Bubuntu/home/jabaile/work/TypeScript-go/newfile.ts"},
Expand Down
1 change: 1 addition & 0 deletions internal/ls/string_completions.go
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,7 @@ func getStringLiteralCompletionsFromModuleNames(
program *compiler.Program,
) *stringLiteralCompletions {
// !!! needs `getModeForUsageLocationWorker`
// TODO investigate if we will need to update this for pnp, once available
return nil
}

Expand Down
2 changes: 1 addition & 1 deletion internal/lsp/lsproto/lsp.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
type DocumentUri string // !!!

func (uri DocumentUri) FileName() string {
if strings.HasPrefix(string(uri), "file://") {
if strings.HasPrefix(string(uri), "file://") || strings.HasPrefix(string(uri), "zip:") {
parsed := core.Must(url.Parse(string(uri)))
if parsed.Host != "" {
return "//" + parsed.Host + parsed.Path
Expand Down
10 changes: 9 additions & 1 deletion internal/lsp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ import (
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/ls"
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
"github.com/microsoft/typescript-go/internal/pnp"
"github.com/microsoft/typescript-go/internal/project"
"github.com/microsoft/typescript-go/internal/project/ata"
"github.com/microsoft/typescript-go/internal/project/logging"
"github.com/microsoft/typescript-go/internal/tspath"
"github.com/microsoft/typescript-go/internal/vfs"
"github.com/microsoft/typescript-go/internal/vfs/pnpvfs"
"golang.org/x/sync/errgroup"
"golang.org/x/text/language"
)
Expand Down Expand Up @@ -699,6 +701,12 @@ func (s *Server) handleInitialized(ctx context.Context, params *lsproto.Initiali
cwd = s.cwd
}

fs := s.fs
pnpApi := pnp.InitPnpApi(fs, cwd)
if pnpApi != nil {
fs = pnpvfs.From(fs)
}

s.session = project.NewSession(&project.SessionInit{
Options: &project.SessionOptions{
CurrentDirectory: cwd,
Expand All @@ -709,7 +717,7 @@ func (s *Server) handleInitialized(ctx context.Context, params *lsproto.Initiali
LoggingEnabled: true,
DebounceDelay: 500 * time.Millisecond,
},
FS: s.fs,
FS: fs,
Logger: s.logger,
Client: s,
NpmExecutor: s,
Expand Down
67 changes: 63 additions & 4 deletions internal/module/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/diagnostics"
"github.com/microsoft/typescript-go/internal/packagejson"
"github.com/microsoft/typescript-go/internal/pnp"
"github.com/microsoft/typescript-go/internal/semver"
"github.com/microsoft/typescript-go/internal/tspath"
)
Expand Down Expand Up @@ -475,7 +476,7 @@ func (r *resolutionState) resolveNodeLikeWorker() *ResolvedModule {
resolved := r.nodeLoadModuleByRelativeName(r.extensions, candidate, false, true)
return r.createResolvedModule(
resolved,
resolved != nil && strings.Contains(resolved.path, "/node_modules/"),
resolved != nil && (tspath.IsExternalLibraryImport(resolved.path)),
)
}
return r.createResolvedModule(nil, false)
Expand Down Expand Up @@ -914,6 +915,12 @@ func (r *resolutionState) loadModuleFromNearestNodeModulesDirectory(typesScopeOn
}

func (r *resolutionState) loadModuleFromNearestNodeModulesDirectoryWorker(ext extensions, mode core.ResolutionMode, typesScopeOnly bool) *resolved {
pnpApi := pnp.GetPnpApi(r.containingDirectory)
if pnpApi != nil {
// !!! stop at global cache
return r.loadModuleFromImmediateNodeModulesDirectoryPnP(ext, r.containingDirectory, typesScopeOnly)
}

result, _ := tspath.ForEachAncestorDirectory(
r.containingDirectory,
func(directory string) (result *resolved, stop bool) {
Expand Down Expand Up @@ -953,11 +960,52 @@ func (r *resolutionState) loadModuleFromImmediateNodeModulesDirectory(extensions
return continueSearching()
}

/*
With Plug and Play, we directly resolve the path of the moduleName using the PnP API, instead of searching for it in the node_modules directory

See github.com/microsoft/typescript-go/internal/pnp package for more details
*/
func (r *resolutionState) loadModuleFromImmediateNodeModulesDirectoryPnP(extensions extensions, directory string, typesScopeOnly bool) *resolved {
if !typesScopeOnly {
if packageResult := r.loadModuleFromPnpResolution(extensions, r.name, directory); !packageResult.shouldContinueSearching() {
return packageResult
}
}

if extensions&extensionsDeclaration != 0 {
result := r.loadModuleFromPnpResolution(extensionsDeclaration, "@types/"+r.mangleScopedPackageName(r.name), directory)

return result
}

return nil
}

func (r *resolutionState) loadModuleFromPnpResolution(ext extensions, moduleName string, issuer string) *resolved {
pnpApi := pnp.GetPnpApi(issuer)

if pnpApi != nil {
packageName, rest := ParsePackageName(moduleName)
// TODO: bubble up yarn resolution errors, instead of _
packageDirectory, _ := pnpApi.ResolveToUnqualified(packageName, issuer)
if packageDirectory != "" {
candidate := tspath.NormalizePath(tspath.CombinePaths(packageDirectory, rest))
return r.loadModuleFromSpecificNodeModulesDirectoryImpl(ext, true /* nodeModulesDirectoryExists */, candidate, rest, packageDirectory)
}
}

return nil
}

func (r *resolutionState) loadModuleFromSpecificNodeModulesDirectory(ext extensions, moduleName string, nodeModulesDirectory string, nodeModulesDirectoryExists bool) *resolved {
candidate := tspath.NormalizePath(tspath.CombinePaths(nodeModulesDirectory, moduleName))
packageName, rest := ParsePackageName(moduleName)
packageDirectory := tspath.CombinePaths(nodeModulesDirectory, packageName)

return r.loadModuleFromSpecificNodeModulesDirectoryImpl(ext, nodeModulesDirectoryExists, candidate, rest, packageDirectory)
}

func (r *resolutionState) loadModuleFromSpecificNodeModulesDirectoryImpl(ext extensions, nodeModulesDirectoryExists bool, candidate string, rest string, packageDirectory string) *resolved {
var rootPackageInfo *packagejson.InfoCacheEntry
// First look for a nested package.json, as in `node_modules/foo/bar/package.json`
packageInfo := r.getPackageJsonInfo(candidate, !nodeModulesDirectoryExists)
Expand Down Expand Up @@ -1036,7 +1084,7 @@ func (r *resolutionState) loadModuleFromSpecificNodeModulesDirectory(ext extensi
}

func (r *resolutionState) createResolvedModuleHandlingSymlink(resolved *resolved) *ResolvedModule {
isExternalLibraryImport := resolved != nil && strings.Contains(resolved.path, "/node_modules/")
isExternalLibraryImport := resolved != nil && (tspath.IsExternalLibraryImport(resolved.path))
if r.compilerOptions.PreserveSymlinks != core.TSTrue &&
isExternalLibraryImport &&
resolved.originalPath == "" &&
Expand Down Expand Up @@ -1084,7 +1132,7 @@ func (r *resolutionState) createResolvedTypeReferenceDirective(resolved *resolve
resolvedTypeReferenceDirective.ResolvedFileName = resolved.path
resolvedTypeReferenceDirective.Primary = primary
resolvedTypeReferenceDirective.PackageId = resolved.packageId
resolvedTypeReferenceDirective.IsExternalLibraryImport = strings.Contains(resolved.path, "/node_modules/")
resolvedTypeReferenceDirective.IsExternalLibraryImport = tspath.IsExternalLibraryImport(resolved.path)

if r.compilerOptions.PreserveSymlinks != core.TSTrue {
originalPath, resolvedFileName := r.getOriginalAndResolvedFileName(resolved.path)
Expand Down Expand Up @@ -1740,8 +1788,19 @@ func (r *resolutionState) readPackageJsonPeerDependencies(packageJsonInfo *packa
}
nodeModules := packageDirectory[:nodeModulesIndex+len("/node_modules")] + "/"
builder := strings.Builder{}
pnpApi := pnp.GetPnpApi(packageJsonInfo.PackageDirectory)
for name := range peerDependencies.Value {
peerPackageJson := r.getPackageJsonInfo(nodeModules+name /*onlyRecordFailures*/, false)
var peerDependencyPath string

if pnpApi != nil {
peerDependencyPath, _ = pnpApi.ResolveToUnqualified(name, packageDirectory)
}

if peerDependencyPath == "" {
peerDependencyPath = nodeModules + name
}

peerPackageJson := r.getPackageJsonInfo(peerDependencyPath, false /*onlyRecordFailures*/)
if peerPackageJson != nil {
version := peerPackageJson.Contents.Version.Value
builder.WriteString("+")
Expand Down
Loading