199 lines
5.7 KiB
Go
199 lines
5.7 KiB
Go
package packagejson
|
|
|
|
import (
|
|
"sync"
|
|
|
|
"github.com/microsoft/typescript-go/internal/collections"
|
|
"github.com/microsoft/typescript-go/internal/core"
|
|
"github.com/microsoft/typescript-go/internal/diagnostics"
|
|
"github.com/microsoft/typescript-go/internal/semver"
|
|
"github.com/microsoft/typescript-go/internal/tspath"
|
|
)
|
|
|
|
var typeScriptVersion = semver.MustParse(core.Version())
|
|
|
|
type PackageJson struct {
|
|
Fields
|
|
Parseable bool
|
|
versionPaths VersionPaths
|
|
versionTraces []diagnosticAndArgs
|
|
once sync.Once
|
|
}
|
|
|
|
type diagnosticAndArgs struct {
|
|
message *diagnostics.Message
|
|
args []any
|
|
}
|
|
|
|
func (p *PackageJson) GetVersionPaths(trace func(m *diagnostics.Message, args ...any)) VersionPaths {
|
|
p.once.Do(func() {
|
|
if p.Fields.TypesVersions.Type == JSONValueTypeNotPresent {
|
|
p.versionTraces = append(p.versionTraces, diagnosticAndArgs{
|
|
diagnostics.X_package_json_does_not_have_a_0_field,
|
|
[]any{"typesVersions"},
|
|
})
|
|
return
|
|
}
|
|
if p.Fields.TypesVersions.Type != JSONValueTypeObject {
|
|
p.versionTraces = append(p.versionTraces, diagnosticAndArgs{
|
|
diagnostics.Expected_type_of_0_field_in_package_json_to_be_1_got_2,
|
|
[]any{"typesVersions", "object", p.Fields.TypesVersions.Type.String()},
|
|
})
|
|
return
|
|
}
|
|
|
|
p.versionTraces = append(p.versionTraces, diagnosticAndArgs{
|
|
diagnostics.X_package_json_has_a_typesVersions_field_with_version_specific_path_mappings,
|
|
[]any{"typesVersions"},
|
|
})
|
|
|
|
for key, value := range p.Fields.TypesVersions.AsObject().Entries() {
|
|
keyRange, ok := semver.TryParseVersionRange(key)
|
|
if !ok {
|
|
p.versionTraces = append(p.versionTraces, diagnosticAndArgs{
|
|
diagnostics.X_package_json_has_a_typesVersions_entry_0_that_is_not_a_valid_semver_range,
|
|
[]any{key},
|
|
})
|
|
continue
|
|
}
|
|
if keyRange.Test(&typeScriptVersion) {
|
|
if value.Type != JSONValueTypeObject {
|
|
p.versionTraces = append(p.versionTraces, diagnosticAndArgs{
|
|
diagnostics.Expected_type_of_0_field_in_package_json_to_be_1_got_2,
|
|
[]any{"typesVersions['" + key + "']", "object", value.Type.String()},
|
|
})
|
|
return
|
|
}
|
|
p.versionPaths = VersionPaths{
|
|
Version: key,
|
|
pathsJSON: value.AsObject(),
|
|
}
|
|
return
|
|
}
|
|
}
|
|
|
|
p.versionTraces = append(p.versionTraces, diagnosticAndArgs{
|
|
diagnostics.X_package_json_does_not_have_a_typesVersions_entry_that_matches_version_0,
|
|
[]any{core.VersionMajorMinor()},
|
|
})
|
|
})
|
|
if trace != nil {
|
|
for _, msg := range p.versionTraces {
|
|
trace(msg.message, msg.args...)
|
|
}
|
|
}
|
|
return p.versionPaths
|
|
}
|
|
|
|
type VersionPaths struct {
|
|
Version string
|
|
pathsJSON *collections.OrderedMap[string, JSONValue]
|
|
paths *collections.OrderedMap[string, []string]
|
|
}
|
|
|
|
func (v *VersionPaths) Exists() bool {
|
|
return v != nil && v.Version != "" && v.pathsJSON != nil
|
|
}
|
|
|
|
func (v *VersionPaths) GetPaths() *collections.OrderedMap[string, []string] {
|
|
if !v.Exists() {
|
|
return nil
|
|
}
|
|
if v.paths != nil {
|
|
return v.paths
|
|
}
|
|
paths := collections.NewOrderedMapWithSizeHint[string, []string](v.pathsJSON.Size())
|
|
for key, value := range v.pathsJSON.Entries() {
|
|
if value.Type != JSONValueTypeArray {
|
|
continue
|
|
}
|
|
slice := make([]string, len(value.AsArray()))
|
|
for i, path := range value.AsArray() {
|
|
if path.Type != JSONValueTypeString {
|
|
continue
|
|
}
|
|
slice[i] = path.Value.(string)
|
|
}
|
|
paths.Set(key, slice)
|
|
}
|
|
v.paths = paths
|
|
return v.paths
|
|
}
|
|
|
|
type InfoCacheEntry struct {
|
|
PackageDirectory string
|
|
DirectoryExists bool
|
|
Contents *PackageJson
|
|
}
|
|
|
|
func (p *InfoCacheEntry) Exists() bool {
|
|
return p != nil && p.Contents != nil
|
|
}
|
|
|
|
func (p *InfoCacheEntry) GetContents() *PackageJson {
|
|
if p == nil || p.Contents == nil {
|
|
return nil
|
|
}
|
|
return p.Contents
|
|
}
|
|
|
|
func (p *InfoCacheEntry) GetDirectory() string {
|
|
if p == nil {
|
|
return ""
|
|
}
|
|
return p.PackageDirectory
|
|
}
|
|
|
|
// WithPackageDirectory returns an entry whose PackageDirectory matches the
|
|
// caller's value. The package.json info cache is keyed by the canonical
|
|
// path of the package.json file, but multiple callers may look up the same
|
|
// package.json using directory paths that differ only by a trailing
|
|
// separator (e.g. "node_modules/preact/compat" vs
|
|
// "node_modules/preact/compat/"). Because the cache uses first-writer-wins
|
|
// semantics, a later caller may receive an entry whose PackageDirectory
|
|
// doesn't match its own candidate path. Downstream code compares the
|
|
// candidate against PackageDirectory, so we must return a corrected
|
|
// shallow copy when they diverge.
|
|
// See https://github.com/microsoft/TypeScript/pull/50740.
|
|
func (p *InfoCacheEntry) WithPackageDirectory(packageDirectory string) *InfoCacheEntry {
|
|
if p.PackageDirectory == packageDirectory {
|
|
return p
|
|
}
|
|
return &InfoCacheEntry{
|
|
PackageDirectory: packageDirectory,
|
|
DirectoryExists: p.DirectoryExists,
|
|
Contents: p.Contents,
|
|
}
|
|
}
|
|
|
|
type InfoCache struct {
|
|
cache collections.SyncMap[tspath.Path, *InfoCacheEntry]
|
|
currentDirectory string
|
|
useCaseSensitiveFileNames bool
|
|
}
|
|
|
|
func NewInfoCache(currentDirectory string, useCaseSensitiveFileNames bool) *InfoCache {
|
|
return &InfoCache{
|
|
currentDirectory: currentDirectory,
|
|
useCaseSensitiveFileNames: useCaseSensitiveFileNames,
|
|
}
|
|
}
|
|
|
|
func (p *InfoCache) Get(packageJsonPath string) *InfoCacheEntry {
|
|
key := tspath.ToPath(packageJsonPath, p.currentDirectory, p.useCaseSensitiveFileNames)
|
|
if value, ok := p.cache.Load(key); ok {
|
|
return value
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *InfoCache) Set(packageJsonPath string, info *InfoCacheEntry) *InfoCacheEntry {
|
|
key := tspath.ToPath(packageJsonPath, p.currentDirectory, p.useCaseSensitiveFileNames)
|
|
actual, _ := p.cache.LoadOrStore(key, info)
|
|
return actual
|
|
}
|
|
|
|
func (p *InfoCache) Range(f func(key tspath.Path, value *InfoCacheEntry) bool) {
|
|
p.cache.Range(f)
|
|
}
|