vendor tsgo
This commit is contained in:
500
tools/tsgo/internal/project/ata/ata.go
Normal file
500
tools/tsgo/internal/project/ata/ata.go
Normal file
@@ -0,0 +1,500 @@
|
||||
package ata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/collections"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/json"
|
||||
"github.com/microsoft/typescript-go/internal/module"
|
||||
"github.com/microsoft/typescript-go/internal/project/logging"
|
||||
"github.com/microsoft/typescript-go/internal/semver"
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
"github.com/microsoft/typescript-go/internal/vfs"
|
||||
)
|
||||
|
||||
type TypingsInfo struct {
|
||||
TypeAcquisition *core.TypeAcquisition
|
||||
CompilerOptions *core.CompilerOptions
|
||||
UnresolvedImports *collections.Set[string]
|
||||
}
|
||||
|
||||
func (ti TypingsInfo) Equals(other TypingsInfo) bool {
|
||||
return ti.TypeAcquisition.Equals(other.TypeAcquisition) &&
|
||||
ti.CompilerOptions.GetAllowJS() == other.CompilerOptions.GetAllowJS() &&
|
||||
ti.UnresolvedImports.Equals(other.UnresolvedImports)
|
||||
}
|
||||
|
||||
type CachedTyping struct {
|
||||
TypingsLocation string
|
||||
Version *semver.Version
|
||||
}
|
||||
|
||||
type TypingsInstallerOptions struct {
|
||||
TypingsLocation string
|
||||
ThrottleLimit int
|
||||
}
|
||||
|
||||
type NpmExecutor interface {
|
||||
NpmInstall(cwd string, args []string) ([]byte, error)
|
||||
}
|
||||
|
||||
type TypingsInstallerHost interface {
|
||||
NpmExecutor
|
||||
module.ResolutionHost
|
||||
}
|
||||
|
||||
type TypingsInstaller struct {
|
||||
typingsLocation string
|
||||
host TypingsInstallerHost
|
||||
|
||||
initOnce sync.Once
|
||||
|
||||
packageNameToTypingLocation collections.SyncMap[string, *CachedTyping]
|
||||
missingTypingsSet collections.SyncMap[string, bool]
|
||||
|
||||
typesRegistry map[string]map[string]string
|
||||
|
||||
installRunCount atomic.Int32
|
||||
concurrencySemaphore chan struct{}
|
||||
}
|
||||
|
||||
func NewTypingsInstaller(options *TypingsInstallerOptions, host TypingsInstallerHost) *TypingsInstaller {
|
||||
return &TypingsInstaller{
|
||||
typingsLocation: options.TypingsLocation,
|
||||
host: host,
|
||||
concurrencySemaphore: make(chan struct{}, options.ThrottleLimit),
|
||||
}
|
||||
}
|
||||
|
||||
func (ti *TypingsInstaller) IsKnownTypesPackageName(projectID tspath.Path, name string, fs vfs.FS, logger logging.Logger) bool {
|
||||
// We want to avoid looking this up in the registry as that is expensive. So first check that it's actually an NPM package.
|
||||
validationResult, _, _ := ValidatePackageName(name)
|
||||
if validationResult != NameOk {
|
||||
return false
|
||||
}
|
||||
// Strada did this lazily - is that needed here to not waiting on and returning false on first request
|
||||
ti.init(string(projectID), fs, logger)
|
||||
_, ok := ti.typesRegistry[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
// !!! sheetal currently we use latest instead of core.VersionMajorMinor()
|
||||
const tsVersionToUse = "latest"
|
||||
|
||||
type TypingsInstallRequest struct {
|
||||
ProjectID tspath.Path
|
||||
TypingsInfo *TypingsInfo
|
||||
FileNames []string
|
||||
ProjectRootPath string
|
||||
CompilerOptions *core.CompilerOptions
|
||||
CurrentDirectory string
|
||||
GetScriptKind func(string) core.ScriptKind
|
||||
FS vfs.FS
|
||||
Logger logging.Logger
|
||||
}
|
||||
|
||||
type TypingsInstallResult struct {
|
||||
TypingsFiles []string
|
||||
FilesToWatch []string
|
||||
}
|
||||
|
||||
func (ti *TypingsInstaller) InstallTypings(request *TypingsInstallRequest) (*TypingsInstallResult, error) {
|
||||
result, err := ti.discoverAndInstallTypings(request)
|
||||
if err == nil {
|
||||
slices.Sort(result.TypingsFiles)
|
||||
slices.Sort(result.FilesToWatch)
|
||||
request.Logger.Log("ATA:: Got install request for: " + string(request.ProjectID))
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (ti *TypingsInstaller) discoverAndInstallTypings(request *TypingsInstallRequest) (*TypingsInstallResult, error) {
|
||||
ti.init(string(request.ProjectID), request.FS, request.Logger)
|
||||
|
||||
cachedTypingPaths, newTypingNames, filesToWatch := DiscoverTypings(
|
||||
request.FS,
|
||||
request.Logger,
|
||||
request.TypingsInfo,
|
||||
request.FileNames,
|
||||
request.ProjectRootPath,
|
||||
&ti.packageNameToTypingLocation,
|
||||
ti.typesRegistry,
|
||||
)
|
||||
|
||||
requestId := ti.installRunCount.Add(1)
|
||||
// install typings
|
||||
if len(newTypingNames) > 0 {
|
||||
filteredTypings := ti.filterTypings(request.ProjectID, request.Logger, newTypingNames)
|
||||
if len(filteredTypings) != 0 {
|
||||
typingsFiles, err := ti.installTypings(request.ProjectID, request.TypingsInfo, requestId, cachedTypingPaths, filteredTypings, request.Logger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &TypingsInstallResult{
|
||||
TypingsFiles: typingsFiles,
|
||||
FilesToWatch: filesToWatch,
|
||||
}, nil
|
||||
}
|
||||
request.Logger.Log("ATA:: All typings are known to be missing or invalid - no need to install more typings")
|
||||
} else {
|
||||
request.Logger.Log("ATA:: No new typings were requested as a result of typings discovery")
|
||||
}
|
||||
|
||||
return &TypingsInstallResult{
|
||||
TypingsFiles: cachedTypingPaths,
|
||||
FilesToWatch: filesToWatch,
|
||||
}, nil
|
||||
// !!! sheetal events to send
|
||||
// this.event(response, "setTypings");
|
||||
}
|
||||
|
||||
func (ti *TypingsInstaller) installTypings(
|
||||
projectID tspath.Path,
|
||||
typingsInfo *TypingsInfo,
|
||||
requestID int32,
|
||||
currentlyCachedTypings []string,
|
||||
filteredTypings []string,
|
||||
logger logging.Logger,
|
||||
) ([]string, error) {
|
||||
// !!! sheetal events to send
|
||||
// send progress event
|
||||
// this.sendResponse({
|
||||
// kind: EventBeginInstallTypes,
|
||||
// eventId: requestId,
|
||||
// typingsInstallerVersion: version,
|
||||
// projectName: req.projectName,
|
||||
// } as BeginInstallTypes);
|
||||
|
||||
// const body: protocol.BeginInstallTypesEventBody = {
|
||||
// eventId: response.eventId,
|
||||
// packages: response.packagesToInstall,
|
||||
// };
|
||||
// const eventName: protocol.BeginInstallTypesEventName = "beginInstallTypes";
|
||||
// this.event(body, eventName);
|
||||
|
||||
scopedTypings := make([]string, len(filteredTypings))
|
||||
for i, packageName := range filteredTypings {
|
||||
scopedTypings[i] = fmt.Sprintf("@types/%s@%s", packageName, tsVersionToUse) // @tscore.VersionMajorMinor) // This is normally @tsVersionMajorMinor but for now lets use latest
|
||||
}
|
||||
|
||||
if packageNames, ok := ti.installWorker(projectID, requestID, scopedTypings, logger); ok {
|
||||
logger.Log(fmt.Sprintf("ATA:: Installed typings %v", packageNames))
|
||||
var installedTypingFiles []string
|
||||
resolver := module.NewResolver(ti.host, &core.CompilerOptions{ModuleResolution: core.ModuleResolutionKindNodeNext}, "", "")
|
||||
for _, packageName := range filteredTypings {
|
||||
typingFile := ti.typingToFileName(resolver, packageName)
|
||||
if typingFile == "" {
|
||||
logger.Log(fmt.Sprintf("ATA:: Failed to find typing file for package '%s'", packageName))
|
||||
ti.missingTypingsSet.Store(packageName, true)
|
||||
continue
|
||||
}
|
||||
|
||||
// packageName is guaranteed to exist in typesRegistry by filterTypings
|
||||
distTags := ti.typesRegistry[packageName]
|
||||
useVersion, ok := distTags["ts"+core.VersionMajorMinor()]
|
||||
if !ok {
|
||||
useVersion = distTags["latest"]
|
||||
}
|
||||
newVersion := semver.MustParse(useVersion)
|
||||
newTyping := &CachedTyping{TypingsLocation: typingFile, Version: &newVersion}
|
||||
ti.packageNameToTypingLocation.Store(packageName, newTyping)
|
||||
installedTypingFiles = append(installedTypingFiles, typingFile)
|
||||
}
|
||||
logger.Log(fmt.Sprintf("ATA:: Installed typing files %v", installedTypingFiles))
|
||||
|
||||
return append(currentlyCachedTypings, installedTypingFiles...), nil
|
||||
}
|
||||
|
||||
// DO we really need these events
|
||||
// this.event(response, "setTypings");
|
||||
logger.Log(fmt.Sprintf("ATA:: install request failed, marking packages as missing to prevent repeated requests: %v", filteredTypings))
|
||||
for _, typing := range filteredTypings {
|
||||
ti.missingTypingsSet.Store(typing, true)
|
||||
}
|
||||
|
||||
return nil, errors.New("npm install failed")
|
||||
|
||||
// !!! sheetal events to send
|
||||
// const response: EndInstallTypes = {
|
||||
// kind: EventEndInstallTypes,
|
||||
// eventId: requestId,
|
||||
// projectName: req.projectName,
|
||||
// packagesToInstall: scopedTypings,
|
||||
// installSuccess: ok,
|
||||
// typingsInstallerVersion: version,
|
||||
// };
|
||||
// this.sendResponse(response);
|
||||
|
||||
// if (this.telemetryEnabled) {
|
||||
// const body: protocol.TypingsInstalledTelemetryEventBody = {
|
||||
// telemetryEventName: "typingsInstalled",
|
||||
// payload: {
|
||||
// installedPackages: response.packagesToInstall.join(","),
|
||||
// installSuccess: response.installSuccess,
|
||||
// typingsInstallerVersion: response.typingsInstallerVersion,
|
||||
// },
|
||||
// };
|
||||
// const eventName: protocol.TelemetryEventName = "telemetry";
|
||||
// this.event(body, eventName);
|
||||
// }
|
||||
|
||||
// const body: protocol.EndInstallTypesEventBody = {
|
||||
// eventId: response.eventId,
|
||||
// packages: response.packagesToInstall,
|
||||
// success: response.installSuccess,
|
||||
// };
|
||||
// const eventName: protocol.EndInstallTypesEventName = "endInstallTypes";
|
||||
// this.event(body, eventName);
|
||||
}
|
||||
|
||||
func (ti *TypingsInstaller) installWorker(
|
||||
projectID tspath.Path,
|
||||
requestId int32,
|
||||
packageNames []string,
|
||||
logger logging.Logger,
|
||||
) ([]string, bool) {
|
||||
logger.Log(fmt.Sprintf("ATA:: #%d with cwd: %s arguments: %v", requestId, ti.typingsLocation, packageNames))
|
||||
ctx := context.Background()
|
||||
err := installNpmPackages(ctx, packageNames, ti.concurrencySemaphore, func(packageNames []string) error {
|
||||
var npmArgs []string
|
||||
npmArgs = append(npmArgs, "install", "--ignore-scripts")
|
||||
npmArgs = append(npmArgs, packageNames...)
|
||||
npmArgs = append(npmArgs, "--save-dev", "--user-agent=\"typesInstaller/"+core.Version()+"\"")
|
||||
output, err := ti.host.NpmInstall(ti.typingsLocation, npmArgs)
|
||||
if err != nil {
|
||||
logger.Log(fmt.Sprintf("ATA:: Output is: %s", output))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
logger.Log(fmt.Sprintf("TI:: npm install #%d completed", requestId))
|
||||
return packageNames, err == nil
|
||||
}
|
||||
|
||||
func installNpmPackages(
|
||||
ctx context.Context,
|
||||
packageNames []string,
|
||||
concurrencySemaphore chan struct{},
|
||||
installPackages func(packages []string) error,
|
||||
) error {
|
||||
tg := core.NewThrottleGroup(ctx, concurrencySemaphore)
|
||||
|
||||
currentCommandStart := 0
|
||||
currentCommandEnd := 0
|
||||
currentCommandSize := 100
|
||||
|
||||
for _, packageName := range packageNames {
|
||||
currentCommandSize = currentCommandSize + len(packageName) + 1
|
||||
if currentCommandSize < 8000 {
|
||||
currentCommandEnd++
|
||||
} else {
|
||||
packages := packageNames[currentCommandStart:currentCommandEnd]
|
||||
tg.Go(func() error {
|
||||
return installPackages(packages)
|
||||
})
|
||||
currentCommandStart = currentCommandEnd
|
||||
currentCommandSize = 100 + len(packageName) + 1
|
||||
currentCommandEnd++
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the final batch
|
||||
if currentCommandStart < len(packageNames) {
|
||||
packages := packageNames[currentCommandStart:currentCommandEnd]
|
||||
tg.Go(func() error {
|
||||
return installPackages(packages)
|
||||
})
|
||||
}
|
||||
|
||||
return tg.Wait()
|
||||
}
|
||||
|
||||
func (ti *TypingsInstaller) filterTypings(
|
||||
projectID tspath.Path,
|
||||
logger logging.Logger,
|
||||
typingsToInstall []string,
|
||||
) []string {
|
||||
var result []string
|
||||
for _, typing := range typingsToInstall {
|
||||
typingKey := module.MangleScopedPackageName(typing)
|
||||
if _, ok := ti.missingTypingsSet.Load(typingKey); ok {
|
||||
logger.Log(fmt.Sprintf("ATA:: '%s':: '%s' is in missingTypingsSet - skipping...", typing, typingKey))
|
||||
continue
|
||||
}
|
||||
validationResult, name, isScopeName := ValidatePackageName(typing)
|
||||
if validationResult != NameOk {
|
||||
// add typing name to missing set so we won't process it again
|
||||
ti.missingTypingsSet.Store(typingKey, true)
|
||||
logger.Log("ATA:: " + renderPackageNameValidationFailure(typing, validationResult, name, isScopeName))
|
||||
continue
|
||||
}
|
||||
typesRegistryEntry, ok := ti.typesRegistry[typingKey]
|
||||
if !ok {
|
||||
logger.Log(fmt.Sprintf("ATA:: '%s':: Entry for package '%s' does not exist in local types registry - skipping...", typing, typingKey))
|
||||
continue
|
||||
}
|
||||
if typingLocation, ok := ti.packageNameToTypingLocation.Load(typingKey); ok && isTypingUpToDate(typingLocation, typesRegistryEntry) {
|
||||
logger.Log(fmt.Sprintf("ATA:: '%s':: '%s' already has an up-to-date typing - skipping...", typing, typingKey))
|
||||
continue
|
||||
}
|
||||
result = append(result, typingKey)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (ti *TypingsInstaller) init(projectID string, fs vfs.FS, logger logging.Logger) {
|
||||
ti.initOnce.Do(func() {
|
||||
logger.Log("ATA:: Global cache location '" + ti.typingsLocation + "'") //, safe file path '" + safeListPath + "', types map path '" + typesMapLocation + "`")
|
||||
ti.processCacheLocation(projectID, fs, logger)
|
||||
|
||||
// !!! sheetal handle npm path here if we would support it
|
||||
// // If the NPM path contains spaces and isn't wrapped in quotes, do so.
|
||||
// if (this.npmPath.includes(" ") && this.npmPath[0] !== `"`) {
|
||||
// this.npmPath = `"${this.npmPath}"`;
|
||||
// }
|
||||
// if (this.log.isEnabled()) {
|
||||
// this.log.writeLine(`Process id: ${process.pid}`);
|
||||
// this.log.writeLine(`NPM location: ${this.npmPath} (explicit '${ts.server.Arguments.NpmLocation}' ${npmLocation === undefined ? "not " : ""} provided)`);
|
||||
// this.log.writeLine(`validateDefaultNpmLocation: ${validateDefaultNpmLocation}`);
|
||||
// }
|
||||
|
||||
ti.ensureTypingsLocationExists(fs, logger)
|
||||
logger.Log("ATA:: Updating types-registry@latest npm package...")
|
||||
if _, err := ti.host.NpmInstall(ti.typingsLocation, []string{"install", "--ignore-scripts", "types-registry@latest"}); err == nil {
|
||||
logger.Log("ATA:: Updated types-registry npm package")
|
||||
} else {
|
||||
logger.Log(fmt.Sprintf("ATA:: Error updating types-registry package: %v", err))
|
||||
// !!! sheetal events to send
|
||||
// // store error info to report it later when it is known that server is already listening to events from typings installer
|
||||
// this.delayedInitializationError = {
|
||||
// kind: "event::initializationFailed",
|
||||
// message: (e as Error).message,
|
||||
// stack: (e as Error).stack,
|
||||
// };
|
||||
|
||||
// const body: protocol.TypesInstallerInitializationFailedEventBody = {
|
||||
// message: response.message,
|
||||
// };
|
||||
// const eventName: protocol.TypesInstallerInitializationFailedEventName = "typesInstallerInitializationFailed";
|
||||
// this.event(body, eventName);
|
||||
}
|
||||
|
||||
ti.typesRegistry = ti.loadTypesRegistryFile(fs, logger)
|
||||
})
|
||||
}
|
||||
|
||||
type npmConfig struct {
|
||||
DevDependencies map[string]any `json:"devDependencies"`
|
||||
}
|
||||
|
||||
type npmDependecyEntry struct {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
type npmLock struct {
|
||||
Dependencies map[string]npmDependecyEntry `json:"dependencies"`
|
||||
Packages map[string]npmDependecyEntry `json:"packages"`
|
||||
}
|
||||
|
||||
func (ti *TypingsInstaller) processCacheLocation(projectID string, fs vfs.FS, logger logging.Logger) {
|
||||
logger.Log("ATA:: Processing cache location " + ti.typingsLocation)
|
||||
packageJson := tspath.CombinePaths(ti.typingsLocation, "package.json")
|
||||
packageLockJson := tspath.CombinePaths(ti.typingsLocation, "package-lock.json")
|
||||
logger.Log("ATA:: Trying to find '" + packageJson + "'...")
|
||||
if fs.FileExists(packageJson) && fs.FileExists(packageLockJson) {
|
||||
var npmConfig npmConfig
|
||||
npmConfigContents := parseNpmConfigOrLock(fs, logger, packageJson, &npmConfig)
|
||||
var npmLock npmLock
|
||||
npmLockContents := parseNpmConfigOrLock(fs, logger, packageLockJson, &npmLock)
|
||||
|
||||
logger.Log("ATA:: Loaded content of " + packageJson + ": " + npmConfigContents)
|
||||
logger.Log("ATA:: Loaded content of " + packageLockJson + ": " + npmLockContents)
|
||||
|
||||
// !!! sheetal strada uses Node10
|
||||
resolver := module.NewResolver(ti.host, &core.CompilerOptions{ModuleResolution: core.ModuleResolutionKindNodeNext}, "", "")
|
||||
if npmConfig.DevDependencies != nil && (npmLock.Packages != nil || npmLock.Dependencies != nil) {
|
||||
for key := range npmConfig.DevDependencies {
|
||||
npmLockValue, npmLockValueExists := npmLock.Packages["node_modules/"+key]
|
||||
if !npmLockValueExists {
|
||||
npmLockValue, npmLockValueExists = npmLock.Dependencies[key]
|
||||
}
|
||||
if !npmLockValueExists {
|
||||
// if package in package.json but not package-lock.json, skip adding to cache so it is reinstalled on next use
|
||||
continue
|
||||
}
|
||||
// key is @types/<package name>
|
||||
packageName := tspath.GetBaseFileName(key)
|
||||
if packageName == "" {
|
||||
continue
|
||||
}
|
||||
typingFile := ti.typingToFileName(resolver, packageName)
|
||||
if typingFile == "" {
|
||||
ti.missingTypingsSet.Store(packageName, true)
|
||||
continue
|
||||
}
|
||||
if existingTypingFile, existingTypingsFilePresent := ti.packageNameToTypingLocation.Load(packageName); existingTypingsFilePresent {
|
||||
if existingTypingFile.TypingsLocation == typingFile {
|
||||
continue
|
||||
}
|
||||
logger.Log("ATA:: New typing for package " + packageName + " from " + typingFile + " conflicts with existing typing file " + existingTypingFile.TypingsLocation)
|
||||
}
|
||||
logger.Log("ATA:: Adding entry into typings cache: " + packageName + " => " + typingFile)
|
||||
version := npmLockValue.Version
|
||||
if version == "" {
|
||||
continue
|
||||
}
|
||||
newVersion := semver.MustParse(version)
|
||||
newTyping := &CachedTyping{TypingsLocation: typingFile, Version: &newVersion}
|
||||
ti.packageNameToTypingLocation.Store(packageName, newTyping)
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.Log("ATA:: Finished processing cache location " + ti.typingsLocation)
|
||||
}
|
||||
|
||||
func parseNpmConfigOrLock[T npmConfig | npmLock](fs vfs.FS, logger logging.Logger, location string, config *T) string {
|
||||
contents, _ := fs.ReadFile(location)
|
||||
_ = json.Unmarshal([]byte(contents), config)
|
||||
return contents
|
||||
}
|
||||
|
||||
func (ti *TypingsInstaller) ensureTypingsLocationExists(fs vfs.FS, logger logging.Logger) {
|
||||
npmConfigPath := tspath.CombinePaths(ti.typingsLocation, "package.json")
|
||||
logger.Log("ATA:: Npm config file: " + npmConfigPath)
|
||||
|
||||
if !fs.FileExists(npmConfigPath) {
|
||||
logger.Log(fmt.Sprintf("ATA:: Npm config file: '%s' is missing, creating new one...", npmConfigPath))
|
||||
err := fs.WriteFile(npmConfigPath, "{ \"private\": true }")
|
||||
if err != nil {
|
||||
logger.Log(fmt.Sprintf("ATA:: Npm config file write failed: %v", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ti *TypingsInstaller) typingToFileName(resolver *module.Resolver, packageName string) string {
|
||||
result, _ := resolver.ResolveModuleName(packageName, tspath.CombinePaths(ti.typingsLocation, "index.d.ts"), core.ModuleKindNone, nil)
|
||||
return result.ResolvedFileName
|
||||
}
|
||||
|
||||
func (ti *TypingsInstaller) loadTypesRegistryFile(fs vfs.FS, logger logging.Logger) map[string]map[string]string {
|
||||
typesRegistryFile := tspath.CombinePaths(ti.typingsLocation, "node_modules/types-registry/index.json")
|
||||
typesRegistryFileContents, ok := fs.ReadFile(typesRegistryFile)
|
||||
if ok {
|
||||
var entries map[string]map[string]map[string]string
|
||||
err := json.Unmarshal([]byte(typesRegistryFileContents), &entries)
|
||||
if err == nil {
|
||||
if typesRegistry, ok := entries["entries"]; ok {
|
||||
return typesRegistry
|
||||
}
|
||||
}
|
||||
logger.Log(fmt.Sprintf("ATA:: Error when loading types registry file '%s': %v", typesRegistryFile, err))
|
||||
} else {
|
||||
logger.Log(fmt.Sprintf("ATA:: Error reading types registry file '%s'", typesRegistryFile))
|
||||
}
|
||||
return map[string]map[string]string{}
|
||||
}
|
||||
767
tools/tsgo/internal/project/ata/ata_test.go
Normal file
767
tools/tsgo/internal/project/ata/ata_test.go
Normal file
@@ -0,0 +1,767 @@
|
||||
package ata_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/bundled"
|
||||
"github.com/microsoft/typescript-go/internal/ls/lsutil"
|
||||
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
|
||||
"github.com/microsoft/typescript-go/internal/project"
|
||||
"github.com/microsoft/typescript-go/internal/testutil/projecttestutil"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestATA(t *testing.T) {
|
||||
t.Parallel()
|
||||
if !bundled.Embedded {
|
||||
t.Skip("bundled files are not embedded")
|
||||
}
|
||||
|
||||
t.Run("local module should not be picked up", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
files := map[string]any{
|
||||
"/user/username/projects/project/app.js": `const c = require('./config');`,
|
||||
"/user/username/projects/project/config.js": `export let x = 1`,
|
||||
"/user/username/projects/project/jsconfig.json": `{
|
||||
"compilerOptions": { "moduleResolution": "commonjs" },
|
||||
"typeAcquisition": { "enable": true }
|
||||
}`,
|
||||
}
|
||||
|
||||
testOptions := &projecttestutil.TypingsInstallerOptions{
|
||||
TypesRegistry: []string{"config"},
|
||||
}
|
||||
|
||||
session, utils := projecttestutil.SetupWithTypingsInstaller(files, testOptions)
|
||||
uri := lsproto.DocumentUri("file:///user/username/projects/project/app.js")
|
||||
content := files["/user/username/projects/project/app.js"].(string)
|
||||
|
||||
// Open the file
|
||||
session.DidOpenFile(context.Background(), uri, 1, content, lsproto.LanguageKindJavaScript)
|
||||
session.WaitForBackgroundTasks()
|
||||
ls, err := session.GetLanguageService(context.Background(), uri)
|
||||
assert.NilError(t, err)
|
||||
// Verify the local config.js file is included in the program
|
||||
program := ls.GetProgram()
|
||||
assert.Assert(t, program != nil)
|
||||
configFile := program.GetSourceFile("/user/username/projects/project/config.js")
|
||||
assert.Assert(t, configFile != nil, "local config.js should be included")
|
||||
|
||||
// Verify that only types-registry was installed (no @types/config since it's a local module)
|
||||
npmCalls := utils.NpmExecutor().NpmInstallCalls()
|
||||
assert.Equal(t, len(npmCalls), 1)
|
||||
assert.Equal(t, npmCalls[0].Args[2], "types-registry@latest")
|
||||
})
|
||||
|
||||
t.Run("configured projects", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := map[string]any{
|
||||
"/user/username/projects/project/app.js": ``,
|
||||
"/user/username/projects/project/tsconfig.json": `{
|
||||
"compilerOptions": { "allowJs": true },
|
||||
"typeAcquisition": { "enable": true },
|
||||
}`,
|
||||
"/user/username/projects/project/package.json": `{
|
||||
"name": "test",
|
||||
"dependencies": {
|
||||
"jquery": "^3.1.0"
|
||||
}
|
||||
}`,
|
||||
}
|
||||
|
||||
session, utils := projecttestutil.SetupWithTypingsInstaller(files, &projecttestutil.TypingsInstallerOptions{
|
||||
PackageToFile: map[string]string{
|
||||
"jquery": `declare const $: { x: number }`,
|
||||
},
|
||||
})
|
||||
|
||||
session.DidOpenFile(context.Background(), lsproto.DocumentUri("file:///user/username/projects/project/app.js"), 1, files["/user/username/projects/project/app.js"].(string), lsproto.LanguageKindJavaScript)
|
||||
session.WaitForBackgroundTasks()
|
||||
npmCalls := utils.NpmExecutor().NpmInstallCalls()
|
||||
assert.Equal(t, len(npmCalls), 2)
|
||||
assert.Equal(t, npmCalls[0].Cwd, projecttestutil.TestTypingsLocation)
|
||||
assert.Equal(t, npmCalls[0].Args[2], "types-registry@latest")
|
||||
assert.Equal(t, npmCalls[1].Cwd, projecttestutil.TestTypingsLocation)
|
||||
assert.Assert(t, slices.Contains(npmCalls[1].Args, "@types/jquery@latest"))
|
||||
assert.Equal(t, len(utils.Client().RefreshDiagnosticsCalls()), 1)
|
||||
})
|
||||
|
||||
t.Run("inferred projects", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := map[string]any{
|
||||
"/user/username/projects/project/app.js": ``,
|
||||
"/user/username/projects/project/package.json": `{
|
||||
"name": "test",
|
||||
"dependencies": {
|
||||
"jquery": "^3.1.0"
|
||||
}
|
||||
}`,
|
||||
}
|
||||
|
||||
session, utils := projecttestutil.SetupWithTypingsInstaller(files, &projecttestutil.TypingsInstallerOptions{
|
||||
PackageToFile: map[string]string{
|
||||
"jquery": `declare const $: { x: number }`,
|
||||
},
|
||||
})
|
||||
|
||||
session.DidOpenFile(context.Background(), lsproto.DocumentUri("file:///user/username/projects/project/app.js"), 1, files["/user/username/projects/project/app.js"].(string), lsproto.LanguageKindJavaScript)
|
||||
session.WaitForBackgroundTasks()
|
||||
// Check that npm install was called twice
|
||||
calls := utils.NpmExecutor().NpmInstallCalls()
|
||||
assert.Equal(t, 2, len(calls), "Expected exactly 2 npm install calls")
|
||||
assert.Equal(t, calls[0].Cwd, projecttestutil.TestTypingsLocation)
|
||||
assert.DeepEqual(t, calls[0].Args, []string{"install", "--ignore-scripts", "types-registry@latest"})
|
||||
assert.Equal(t, calls[1].Cwd, projecttestutil.TestTypingsLocation)
|
||||
assert.Equal(t, calls[1].Args[2], "@types/jquery@latest")
|
||||
|
||||
// Verify the types file was installed
|
||||
ls, err := session.GetLanguageService(context.Background(), lsproto.DocumentUri("file:///user/username/projects/project/app.js"))
|
||||
assert.NilError(t, err)
|
||||
program := ls.GetProgram()
|
||||
jqueryTypesFile := program.GetSourceFile(projecttestutil.TestTypingsLocation + "/node_modules/@types/jquery/index.d.ts")
|
||||
assert.Assert(t, jqueryTypesFile != nil, "jquery types should be installed")
|
||||
})
|
||||
|
||||
t.Run("type acquisition with disableFilenameBasedTypeAcquisition:true", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := map[string]any{
|
||||
"/user/username/projects/project/jquery.js": ``,
|
||||
"/user/username/projects/project/tsconfig.json": `{
|
||||
"compilerOptions": { "allowJs": true },
|
||||
"typeAcquisition": { "enable": true, "disableFilenameBasedTypeAcquisition": true }
|
||||
}`,
|
||||
}
|
||||
|
||||
session, utils := projecttestutil.SetupWithTypingsInstaller(files, &projecttestutil.TypingsInstallerOptions{
|
||||
TypesRegistry: []string{"jquery"},
|
||||
})
|
||||
|
||||
// Should only get types-registry install, no jquery install since filename-based acquisition is disabled
|
||||
session.DidOpenFile(context.Background(), lsproto.DocumentUri("file:///user/username/projects/project/jquery.js"), 1, files["/user/username/projects/project/jquery.js"].(string), lsproto.LanguageKindJavaScript)
|
||||
session.WaitForBackgroundTasks()
|
||||
|
||||
// Check that npm install was called once (only types-registry)
|
||||
calls := utils.NpmExecutor().NpmInstallCalls()
|
||||
assert.Equal(t, 1, len(calls), "Expected exactly 1 npm install call")
|
||||
assert.Equal(t, calls[0].Cwd, projecttestutil.TestTypingsLocation)
|
||||
assert.DeepEqual(t, calls[0].Args, []string{"install", "--ignore-scripts", "types-registry@latest"})
|
||||
})
|
||||
|
||||
t.Run("discover from node_modules", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := map[string]any{
|
||||
"/user/username/projects/project/app.js": "",
|
||||
"/user/username/projects/project/package.json": `{
|
||||
"dependencies": {
|
||||
"jquery": "1.0.0"
|
||||
}
|
||||
}`,
|
||||
"/user/username/projects/project/jsconfig.json": `{}`,
|
||||
"/user/username/projects/project/node_modules/commander/index.js": "",
|
||||
"/user/username/projects/project/node_modules/commander/package.json": `{ "name": "commander" }`,
|
||||
"/user/username/projects/project/node_modules/jquery/index.js": "",
|
||||
"/user/username/projects/project/node_modules/jquery/package.json": `{ "name": "jquery" }`,
|
||||
"/user/username/projects/project/node_modules/jquery/nested/package.json": `{ "name": "nested" }`,
|
||||
}
|
||||
|
||||
session, utils := projecttestutil.SetupWithTypingsInstaller(files, &projecttestutil.TypingsInstallerOptions{
|
||||
TypesRegistry: []string{"nested", "commander"},
|
||||
PackageToFile: map[string]string{
|
||||
"jquery": "declare const jquery: { x: number }",
|
||||
},
|
||||
})
|
||||
|
||||
session.DidOpenFile(context.Background(), lsproto.DocumentUri("file:///user/username/projects/project/app.js"), 1, files["/user/username/projects/project/app.js"].(string), lsproto.LanguageKindJavaScript)
|
||||
session.WaitForBackgroundTasks()
|
||||
|
||||
// Check that npm install was called twice
|
||||
calls := utils.NpmExecutor().NpmInstallCalls()
|
||||
assert.Equal(t, 2, len(calls), "Expected exactly 2 npm install calls")
|
||||
assert.Equal(t, calls[0].Cwd, projecttestutil.TestTypingsLocation)
|
||||
assert.DeepEqual(t, calls[0].Args, []string{"install", "--ignore-scripts", "types-registry@latest"})
|
||||
assert.Equal(t, calls[1].Cwd, projecttestutil.TestTypingsLocation)
|
||||
assert.Equal(t, calls[1].Args[2], "@types/jquery@latest")
|
||||
})
|
||||
|
||||
t.Run("discover from node_modules empty types", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := map[string]any{
|
||||
"/user/username/projects/project/app.js": "",
|
||||
"/user/username/projects/project/package.json": `{"dependencies": {"jquery": "1.0.0"}}`,
|
||||
"/user/username/projects/project/jsconfig.json": `{"compilerOptions": {"types": []}}`,
|
||||
"/user/username/projects/project/node_modules/commander/index.js": "",
|
||||
"/user/username/projects/project/node_modules/commander/package.json": `{ "name": "commander" }`,
|
||||
"/user/username/projects/project/node_modules/jquery/index.js": "",
|
||||
"/user/username/projects/project/node_modules/jquery/package.json": `{ "name": "jquery" }`,
|
||||
"/user/username/projects/project/node_modules/jquery/nested/package.json": `{ "name": "nested" }`,
|
||||
}
|
||||
|
||||
session, utils := projecttestutil.SetupWithTypingsInstaller(files, &projecttestutil.TypingsInstallerOptions{
|
||||
TypesRegistry: []string{"nested", "commander"},
|
||||
PackageToFile: map[string]string{
|
||||
"jquery": "declare const jquery: { x: number }",
|
||||
},
|
||||
})
|
||||
|
||||
session.DidOpenFile(context.Background(), lsproto.DocumentUri("file:///user/username/projects/project/app.js"), 1, files["/user/username/projects/project/app.js"].(string), lsproto.LanguageKindJavaScript)
|
||||
session.WaitForBackgroundTasks()
|
||||
|
||||
// Only types-registry should be installed
|
||||
calls := utils.NpmExecutor().NpmInstallCalls()
|
||||
assert.Equal(t, 1, len(calls))
|
||||
assert.DeepEqual(t, calls[0].Args, []string{"install", "--ignore-scripts", "types-registry@latest"})
|
||||
})
|
||||
|
||||
t.Run("discover from node_modules explicit types", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := map[string]any{
|
||||
"/user/username/projects/project/app.js": "",
|
||||
"/user/username/projects/project/package.json": `{"dependencies": {"jquery": "1.0.0"}}`,
|
||||
"/user/username/projects/project/jsconfig.json": `{"compilerOptions": {"types": ["jquery"]}}`,
|
||||
"/user/username/projects/project/node_modules/commander/index.js": "",
|
||||
"/user/username/projects/project/node_modules/commander/package.json": `{ "name": "commander" }`,
|
||||
"/user/username/projects/project/node_modules/jquery/index.js": "",
|
||||
"/user/username/projects/project/node_modules/jquery/package.json": `{ "name": "jquery" }`,
|
||||
"/user/username/projects/project/node_modules/jquery/nested/package.json": `{ "name": "nested" }`,
|
||||
}
|
||||
|
||||
session, utils := projecttestutil.SetupWithTypingsInstaller(files, &projecttestutil.TypingsInstallerOptions{
|
||||
TypesRegistry: []string{"nested", "commander"},
|
||||
PackageToFile: map[string]string{
|
||||
"jquery": "declare const jquery: { x: number }",
|
||||
},
|
||||
})
|
||||
|
||||
session.DidOpenFile(context.Background(), lsproto.DocumentUri("file:///user/username/projects/project/app.js"), 1, files["/user/username/projects/project/app.js"].(string), lsproto.LanguageKindJavaScript)
|
||||
session.WaitForBackgroundTasks()
|
||||
|
||||
// Only types-registry should be installed
|
||||
calls := utils.NpmExecutor().NpmInstallCalls()
|
||||
assert.Equal(t, 1, len(calls))
|
||||
assert.DeepEqual(t, calls[0].Args, []string{"install", "--ignore-scripts", "types-registry@latest"})
|
||||
})
|
||||
|
||||
t.Run("discover from node_modules empty types has import", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := map[string]any{
|
||||
"/user/username/projects/project/app.js": `import "jquery";`,
|
||||
"/user/username/projects/project/package.json": `{"dependencies": {"jquery": "1.0.0"}}`,
|
||||
"/user/username/projects/project/jsconfig.json": `{"compilerOptions": {"types": []}}`,
|
||||
"/user/username/projects/project/node_modules/commander/index.js": "",
|
||||
"/user/username/projects/project/node_modules/commander/package.json": `{ "name": "commander" }`,
|
||||
"/user/username/projects/project/node_modules/jquery/index.js": "",
|
||||
"/user/username/projects/project/node_modules/jquery/package.json": `{ "name": "jquery" }`,
|
||||
"/user/username/projects/project/node_modules/jquery/nested/package.json": `{ "name": "nested" }`,
|
||||
}
|
||||
|
||||
session, utils := projecttestutil.SetupWithTypingsInstaller(files, &projecttestutil.TypingsInstallerOptions{
|
||||
TypesRegistry: []string{"nested", "commander"},
|
||||
PackageToFile: map[string]string{
|
||||
"jquery": "declare const jquery: { x: number }",
|
||||
},
|
||||
})
|
||||
|
||||
session.DidOpenFile(context.Background(), lsproto.DocumentUri("file:///user/username/projects/project/app.js"), 1, files["/user/username/projects/project/app.js"].(string), lsproto.LanguageKindJavaScript)
|
||||
session.WaitForBackgroundTasks()
|
||||
|
||||
// types-registry + jquery types
|
||||
calls := utils.NpmExecutor().NpmInstallCalls()
|
||||
assert.Equal(t, 2, len(calls))
|
||||
assert.DeepEqual(t, calls[0].Args, []string{"install", "--ignore-scripts", "types-registry@latest"})
|
||||
assert.Assert(t, slices.Contains(calls[1].Args, "@types/jquery@latest"))
|
||||
})
|
||||
|
||||
t.Run("discover from bower_components", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := map[string]any{
|
||||
"/user/username/projects/project/app.js": ``,
|
||||
"/user/username/projects/project/jsconfig.json": `{}`,
|
||||
"/user/username/projects/project/bower_components/jquery/index.js": "",
|
||||
"/user/username/projects/project/bower_components/jquery/bower.json": `{ "name": "jquery" }`,
|
||||
}
|
||||
|
||||
session, utils := projecttestutil.SetupWithTypingsInstaller(files, &projecttestutil.TypingsInstallerOptions{
|
||||
PackageToFile: map[string]string{
|
||||
"jquery": "declare const jquery: { x: number }",
|
||||
},
|
||||
})
|
||||
|
||||
session.DidOpenFile(context.Background(), lsproto.DocumentUri("file:///user/username/projects/project/app.js"), 1, files["/user/username/projects/project/app.js"].(string), lsproto.LanguageKindJavaScript)
|
||||
session.WaitForBackgroundTasks()
|
||||
|
||||
// Check that npm install was called twice
|
||||
calls := utils.NpmExecutor().NpmInstallCalls()
|
||||
assert.Equal(t, 2, len(calls), "Expected exactly 2 npm install calls")
|
||||
assert.Equal(t, calls[0].Cwd, projecttestutil.TestTypingsLocation)
|
||||
assert.DeepEqual(t, calls[0].Args, []string{"install", "--ignore-scripts", "types-registry@latest"})
|
||||
assert.Equal(t, calls[1].Cwd, projecttestutil.TestTypingsLocation)
|
||||
assert.Equal(t, calls[1].Args[2], "@types/jquery@latest")
|
||||
|
||||
// Verify the types file was installed
|
||||
ls, err := session.GetLanguageService(context.Background(), lsproto.DocumentUri("file:///user/username/projects/project/app.js"))
|
||||
assert.NilError(t, err)
|
||||
jqueryTypesFile := ls.GetProgram().GetSourceFile(projecttestutil.TestTypingsLocation + "/node_modules/@types/jquery/index.d.ts")
|
||||
assert.Assert(t, jqueryTypesFile != nil, "jquery types should be installed")
|
||||
})
|
||||
|
||||
t.Run("discover from bower.json", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := map[string]any{
|
||||
"/user/username/projects/project/app.js": ``,
|
||||
"/user/username/projects/project/jsconfig.json": `{}`,
|
||||
"/user/username/projects/project/bower.json": `{
|
||||
"dependencies": {
|
||||
"jquery": "^3.1.0"
|
||||
}
|
||||
}`,
|
||||
}
|
||||
|
||||
session, utils := projecttestutil.SetupWithTypingsInstaller(files, &projecttestutil.TypingsInstallerOptions{
|
||||
PackageToFile: map[string]string{
|
||||
"jquery": "declare const jquery: { x: number }",
|
||||
},
|
||||
})
|
||||
|
||||
session.DidOpenFile(context.Background(), lsproto.DocumentUri("file:///user/username/projects/project/app.js"), 1, files["/user/username/projects/project/app.js"].(string), lsproto.LanguageKindJavaScript)
|
||||
session.WaitForBackgroundTasks()
|
||||
|
||||
// Check that npm install was called twice
|
||||
calls := utils.NpmExecutor().NpmInstallCalls()
|
||||
assert.Equal(t, 2, len(calls), "Expected exactly 2 npm install calls")
|
||||
assert.Equal(t, calls[0].Cwd, projecttestutil.TestTypingsLocation)
|
||||
assert.DeepEqual(t, calls[0].Args, []string{"install", "--ignore-scripts", "types-registry@latest"})
|
||||
assert.Equal(t, calls[1].Cwd, projecttestutil.TestTypingsLocation)
|
||||
assert.Equal(t, calls[1].Args[2], "@types/jquery@latest")
|
||||
|
||||
// Verify the types file was installed
|
||||
ls, err := session.GetLanguageService(context.Background(), lsproto.DocumentUri("file:///user/username/projects/project/app.js"))
|
||||
assert.NilError(t, err)
|
||||
jqueryTypesFile := ls.GetProgram().GetSourceFile(projecttestutil.TestTypingsLocation + "/node_modules/@types/jquery/index.d.ts")
|
||||
assert.Assert(t, jqueryTypesFile != nil, "jquery types should be installed")
|
||||
})
|
||||
|
||||
t.Run("Malformed package.json should be watched", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := map[string]any{
|
||||
"/user/username/projects/project/app.js": "",
|
||||
"/user/username/projects/project/package.json": `{"dependencies": { "co } }`,
|
||||
}
|
||||
|
||||
session, utils := projecttestutil.SetupWithTypingsInstaller(files, &projecttestutil.TypingsInstallerOptions{
|
||||
PackageToFile: map[string]string{
|
||||
"commander": "export let x: number",
|
||||
},
|
||||
})
|
||||
|
||||
uri := lsproto.DocumentUri("file:///user/username/projects/project/app.js")
|
||||
session.DidOpenFile(context.Background(), uri, 1, files["/user/username/projects/project/app.js"].(string), lsproto.LanguageKindJavaScript)
|
||||
session.WaitForBackgroundTasks()
|
||||
|
||||
// Initially only types-registry update attempted
|
||||
calls := utils.NpmExecutor().NpmInstallCalls()
|
||||
assert.Equal(t, 1, len(calls))
|
||||
assert.DeepEqual(t, calls[0].Args, []string{"install", "--ignore-scripts", "types-registry@latest"})
|
||||
|
||||
// Fix package.json and notify watcher
|
||||
assert.NilError(t, utils.FS().WriteFile(
|
||||
"/user/username/projects/project/package.json",
|
||||
`{ "dependencies": { "commander": "0.0.2" } }`,
|
||||
))
|
||||
session.DidChangeWatchedFiles(context.Background(), []*lsproto.FileEvent{{
|
||||
Type: lsproto.FileChangeTypeChanged,
|
||||
Uri: lsproto.DocumentUri("file:///user/username/projects/project/package.json"),
|
||||
}})
|
||||
// diagnostics refresh triggered - simulate by getting the language service
|
||||
_, _ = session.GetLanguageService(context.Background(), uri)
|
||||
session.WaitForBackgroundTasks()
|
||||
|
||||
calls = utils.NpmExecutor().NpmInstallCalls()
|
||||
assert.Equal(t, 2, len(calls))
|
||||
assert.Assert(t, slices.Contains(calls[1].Args, "@types/commander@latest"))
|
||||
|
||||
// Verify types file present
|
||||
ls, err := session.GetLanguageService(context.Background(), uri)
|
||||
assert.NilError(t, err)
|
||||
program := ls.GetProgram()
|
||||
assert.Assert(t, program.GetSourceFile(projecttestutil.TestTypingsLocation+"/node_modules/@types/commander/index.d.ts") != nil)
|
||||
})
|
||||
|
||||
t.Run("should redo resolution that resolved to '.js' file after typings are installed", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := map[string]any{
|
||||
"/user/username/projects/project/app.js": `\n import * as commander from "commander";\n `,
|
||||
"/user/username/projects/node_modules/commander/index.js": "module.exports = 0",
|
||||
}
|
||||
|
||||
session, utils := projecttestutil.SetupWithTypingsInstaller(files, &projecttestutil.TypingsInstallerOptions{
|
||||
PackageToFile: map[string]string{
|
||||
"commander": "export let commander: number",
|
||||
},
|
||||
})
|
||||
|
||||
uri := lsproto.DocumentUri("file:///user/username/projects/project/app.js")
|
||||
session.DidOpenFile(context.Background(), uri, 1, files["/user/username/projects/project/app.js"].(string), lsproto.LanguageKindJavaScript)
|
||||
session.WaitForBackgroundTasks()
|
||||
|
||||
calls := utils.NpmExecutor().NpmInstallCalls()
|
||||
assert.Equal(t, 2, len(calls))
|
||||
assert.Assert(t, slices.Contains(calls[1].Args, "@types/commander@latest"))
|
||||
|
||||
ls, err := session.GetLanguageService(context.Background(), uri)
|
||||
assert.NilError(t, err)
|
||||
program := ls.GetProgram()
|
||||
// Types file present
|
||||
assert.Assert(t, program.GetSourceFile(projecttestutil.TestTypingsLocation+"/node_modules/@types/commander/index.d.ts") != nil)
|
||||
// JS resolution should be dropped
|
||||
assert.Assert(t, program.GetSourceFile("/user/username/projects/node_modules/commander/index.js") == nil)
|
||||
})
|
||||
|
||||
t.Run("expired cache entry (inferred project, should install typings)", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := map[string]any{
|
||||
"/user/username/projects/project/app.js": "",
|
||||
"/user/username/projects/project/package.json": `{"name":"test","dependencies":{"jquery":"^3.1.0"}}`,
|
||||
projecttestutil.TestTypingsLocation + "/node_modules/@types/jquery/index.d.ts": "export const x = 10;",
|
||||
projecttestutil.TestTypingsLocation + "/package.json": `{"dependencies":{"types-registry":"^0.1.317"},"devDependencies":{"@types/jquery":"^1.0.0"}}`,
|
||||
projecttestutil.TestTypingsLocation + "/package-lock.json": `{"dependencies":{"@types/jquery":{"version":"1.0.0"}}}`,
|
||||
}
|
||||
|
||||
session, _ := projecttestutil.SetupWithTypingsInstaller(files, &projecttestutil.TypingsInstallerOptions{
|
||||
PackageToFile: map[string]string{
|
||||
"jquery": "export const y = 10",
|
||||
},
|
||||
})
|
||||
|
||||
uri := lsproto.DocumentUri("file:///user/username/projects/project/app.js")
|
||||
session.DidOpenFile(context.Background(), uri, 1, files["/user/username/projects/project/app.js"].(string), lsproto.LanguageKindJavaScript)
|
||||
session.WaitForBackgroundTasks()
|
||||
|
||||
ls, err := session.GetLanguageService(context.Background(), uri)
|
||||
assert.NilError(t, err)
|
||||
program := ls.GetProgram()
|
||||
// Expect updated content from installed typings
|
||||
assert.Equal(t, program.GetSourceFile(projecttestutil.TestTypingsLocation+"/node_modules/@types/jquery/index.d.ts").Text(), "export const y = 10")
|
||||
})
|
||||
|
||||
t.Run("non-expired cache entry (inferred project, should not install typings)", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := map[string]any{
|
||||
"/user/username/projects/project/app.js": "",
|
||||
"/user/username/projects/project/package.json": `{"name":"test","dependencies":{"jquery":"^3.1.0"}}`,
|
||||
projecttestutil.TestTypingsLocation + "/node_modules/@types/jquery/index.d.ts": "export const x = 10;",
|
||||
projecttestutil.TestTypingsLocation + "/package.json": `{"dependencies":{"types-registry":"^0.1.317"},"devDependencies":{"@types/jquery":"^1.3.0"}}`,
|
||||
projecttestutil.TestTypingsLocation + "/package-lock.json": `{"dependencies":{"@types/jquery":{"version":"1.3.0"}}}`,
|
||||
}
|
||||
|
||||
session, _ := projecttestutil.SetupWithTypingsInstaller(files, &projecttestutil.TypingsInstallerOptions{
|
||||
TypesRegistry: []string{"jquery"},
|
||||
})
|
||||
|
||||
uri := lsproto.DocumentUri("file:///user/username/projects/project/app.js")
|
||||
session.DidOpenFile(context.Background(), uri, 1, files["/user/username/projects/project/app.js"].(string), lsproto.LanguageKindJavaScript)
|
||||
session.WaitForBackgroundTasks()
|
||||
|
||||
ls, err := session.GetLanguageService(context.Background(), uri)
|
||||
assert.NilError(t, err)
|
||||
program := ls.GetProgram()
|
||||
// Expect existing content unchanged
|
||||
assert.Equal(t, program.GetSourceFile(projecttestutil.TestTypingsLocation+"/node_modules/@types/jquery/index.d.ts").Text(), "export const x = 10;")
|
||||
})
|
||||
|
||||
t.Run("deduplicate from local @types packages", func(t *testing.T) {
|
||||
t.Skip("Todo - implement removing local @types from include list")
|
||||
t.Parallel()
|
||||
|
||||
files := map[string]any{
|
||||
"/user/username/projects/project/app.js": "",
|
||||
"/user/username/projects/project/node_modules/@types/node/index.d.ts": "declare var node;",
|
||||
"/user/username/projects/project/jsconfig.json": `{
|
||||
"typeAcquisition": { "include": ["node"] }
|
||||
}`,
|
||||
}
|
||||
|
||||
session, utils := projecttestutil.SetupWithTypingsInstaller(files, &projecttestutil.TypingsInstallerOptions{
|
||||
TypesRegistry: []string{"node"},
|
||||
})
|
||||
|
||||
uri := lsproto.DocumentUri("file:///user/username/projects/project/app.js")
|
||||
session.DidOpenFile(context.Background(), uri, 1, files["/user/username/projects/project/app.js"].(string), lsproto.LanguageKindJavaScript)
|
||||
session.WaitForBackgroundTasks()
|
||||
|
||||
// Only the types-registry should be installed; @types/node should NOT be installed since it exists locally
|
||||
npmCalls := utils.NpmExecutor().NpmInstallCalls()
|
||||
assert.Equal(t, len(npmCalls), 1)
|
||||
assert.Equal(t, npmCalls[0].Cwd, projecttestutil.TestTypingsLocation)
|
||||
assert.DeepEqual(t, npmCalls[0].Args, []string{"install", "--ignore-scripts", "types-registry@latest"})
|
||||
|
||||
// And the program should include the local @types/node declaration file
|
||||
ls, err := session.GetLanguageService(context.Background(), uri)
|
||||
assert.NilError(t, err)
|
||||
program := ls.GetProgram()
|
||||
assert.Assert(t, program.GetSourceFile("/user/username/projects/project/node_modules/@types/node/index.d.ts") != nil)
|
||||
})
|
||||
|
||||
t.Run("expired cache entry (inferred project, should install typings) lockfile3", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := map[string]any{
|
||||
"/user/username/projects/project/app.js": "",
|
||||
"/user/username/projects/project/package.json": `{"name":"test","dependencies":{"jquery":"^3.1.0"}}`,
|
||||
projecttestutil.TestTypingsLocation + "/node_modules/@types/jquery/index.d.ts": "export const x = 10;",
|
||||
projecttestutil.TestTypingsLocation + "/package.json": `{"dependencies":{"types-registry":"^0.1.317"},"devDependencies":{"@types/jquery":"^1.0.0"}}`,
|
||||
projecttestutil.TestTypingsLocation + "/package-lock.json": `{"packages":{"node_modules/@types/jquery":{"version":"1.0.0"}}}`,
|
||||
}
|
||||
|
||||
session, _ := projecttestutil.SetupWithTypingsInstaller(files, &projecttestutil.TypingsInstallerOptions{
|
||||
PackageToFile: map[string]string{
|
||||
"jquery": "export const y = 10",
|
||||
},
|
||||
})
|
||||
|
||||
uri := lsproto.DocumentUri("file:///user/username/projects/project/app.js")
|
||||
session.DidOpenFile(context.Background(), uri, 1, files["/user/username/projects/project/app.js"].(string), lsproto.LanguageKindJavaScript)
|
||||
session.WaitForBackgroundTasks()
|
||||
|
||||
ls, err := session.GetLanguageService(context.Background(), uri)
|
||||
assert.NilError(t, err)
|
||||
program := ls.GetProgram()
|
||||
// Expect updated content from installed typings
|
||||
assert.Equal(t, program.GetSourceFile(projecttestutil.TestTypingsLocation+"/node_modules/@types/jquery/index.d.ts").Text(), "export const y = 10")
|
||||
})
|
||||
|
||||
t.Run("non-expired cache entry (inferred project, should not install typings) lockfile3", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := map[string]any{
|
||||
"/user/username/projects/project/app.js": "",
|
||||
"/user/username/projects/project/package.json": `{"name":"test","dependencies":{"jquery":"^3.1.0"}}`,
|
||||
projecttestutil.TestTypingsLocation + "/node_modules/@types/jquery/index.d.ts": "export const x = 10;",
|
||||
projecttestutil.TestTypingsLocation + "/package.json": `{"dependencies":{"types-registry":"^0.1.317"},"devDependencies":{"@types/jquery":"^1.3.0"}}`,
|
||||
projecttestutil.TestTypingsLocation + "/package-lock.json": `{"packages":{"node_modules/@types/jquery":{"version":"1.3.0"}}}`,
|
||||
}
|
||||
|
||||
session, _ := projecttestutil.SetupWithTypingsInstaller(files, &projecttestutil.TypingsInstallerOptions{
|
||||
TypesRegistry: []string{"jquery"},
|
||||
})
|
||||
|
||||
uri := lsproto.DocumentUri("file:///user/username/projects/project/app.js")
|
||||
session.DidOpenFile(context.Background(), uri, 1, files["/user/username/projects/project/app.js"].(string), lsproto.LanguageKindJavaScript)
|
||||
session.WaitForBackgroundTasks()
|
||||
|
||||
ls, err := session.GetLanguageService(context.Background(), uri)
|
||||
assert.NilError(t, err)
|
||||
program := ls.GetProgram()
|
||||
// Expect existing content unchanged
|
||||
assert.Equal(t, program.GetSourceFile(projecttestutil.TestTypingsLocation+"/node_modules/@types/jquery/index.d.ts").Text(), "export const x = 10;")
|
||||
})
|
||||
|
||||
t.Run("should install typings for unresolved imports", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := map[string]any{
|
||||
"/user/username/projects/project/app.js": `
|
||||
import * as fs from "fs";
|
||||
import * as commander from "commander";
|
||||
import * as component from "@ember/component";
|
||||
`,
|
||||
}
|
||||
|
||||
session, utils := projecttestutil.SetupWithTypingsInstaller(files, &projecttestutil.TypingsInstallerOptions{
|
||||
PackageToFile: map[string]string{
|
||||
"node": "export let node: number",
|
||||
"commander": "export let commander: number",
|
||||
"ember__component": "export let ember__component: number",
|
||||
},
|
||||
})
|
||||
|
||||
session.DidOpenFile(context.Background(), lsproto.DocumentUri("file:///user/username/projects/project/app.js"), 1, files["/user/username/projects/project/app.js"].(string), lsproto.LanguageKindJavaScript)
|
||||
session.WaitForBackgroundTasks()
|
||||
|
||||
// Check that npm install was called twice
|
||||
calls := utils.NpmExecutor().NpmInstallCalls()
|
||||
assert.Equal(t, 2, len(calls), "Expected exactly 2 npm install calls")
|
||||
assert.Equal(t, calls[0].Cwd, projecttestutil.TestTypingsLocation)
|
||||
assert.DeepEqual(t, calls[0].Args, []string{"install", "--ignore-scripts", "types-registry@latest"})
|
||||
|
||||
// The second call should install all three packages at once
|
||||
assert.Equal(t, calls[1].Cwd, projecttestutil.TestTypingsLocation)
|
||||
assert.Equal(t, calls[1].Args[0], "install")
|
||||
assert.Equal(t, calls[1].Args[1], "--ignore-scripts")
|
||||
// Check that all three packages are in the install command
|
||||
installArgs := calls[1].Args
|
||||
assert.Assert(t, slices.Contains(installArgs, "@types/ember__component@latest"))
|
||||
assert.Assert(t, slices.Contains(installArgs, "@types/commander@latest"))
|
||||
assert.Assert(t, slices.Contains(installArgs, "@types/node@latest"))
|
||||
|
||||
// Verify the types files were installed
|
||||
ls, err := session.GetLanguageService(context.Background(), lsproto.DocumentUri("file:///user/username/projects/project/app.js"))
|
||||
assert.NilError(t, err)
|
||||
program := ls.GetProgram()
|
||||
nodeTypesFile := program.GetSourceFile(projecttestutil.TestTypingsLocation + "/node_modules/@types/node/index.d.ts")
|
||||
assert.Assert(t, nodeTypesFile != nil, "node types should be installed")
|
||||
commanderTypesFile := program.GetSourceFile(projecttestutil.TestTypingsLocation + "/node_modules/@types/commander/index.d.ts")
|
||||
assert.Assert(t, commanderTypesFile != nil, "commander types should be installed")
|
||||
emberComponentTypesFile := program.GetSourceFile(projecttestutil.TestTypingsLocation + "/node_modules/@types/ember__component/index.d.ts")
|
||||
assert.Assert(t, emberComponentTypesFile != nil, "ember__component types should be installed")
|
||||
})
|
||||
|
||||
// Test that ATA works correctly when `WatchEnabled` is false but `TypingsLocation` is set.
|
||||
// Previously if `WatchEnabled` was false but `TypingsLocation` was set, ATA would run but
|
||||
// crash when cloning file-watcher data for a new snapshot.
|
||||
t.Run("ATA with WatchEnabled false should not panic", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := map[string]any{
|
||||
"/user/username/projects/project/app.js": ``,
|
||||
"/user/username/projects/project/package.json": `{
|
||||
"name": "test",
|
||||
"dependencies": {
|
||||
"jquery": "^3.1.0"
|
||||
}
|
||||
}`,
|
||||
}
|
||||
|
||||
session, utils := projecttestutil.SetupWithOptionsAndTypingsInstaller(files, &project.SessionOptions{
|
||||
CurrentDirectory: "/",
|
||||
DefaultLibraryPath: bundled.LibPath(),
|
||||
TypingsLocation: projecttestutil.TestTypingsLocation,
|
||||
PositionEncoding: lsproto.PositionEncodingKindUTF8,
|
||||
WatchEnabled: false,
|
||||
LoggingEnabled: true,
|
||||
}, &projecttestutil.TypingsInstallerOptions{
|
||||
PackageToFile: map[string]string{
|
||||
"jquery": `declare const $: { x: number }`,
|
||||
},
|
||||
})
|
||||
|
||||
// Open a file to trigger project creation and ATA.
|
||||
session.DidOpenFile(context.Background(), lsproto.DocumentUri("file:///user/username/projects/project/app.js"), 1, files["/user/username/projects/project/app.js"].(string), lsproto.LanguageKindJavaScript)
|
||||
session.WaitForBackgroundTasks()
|
||||
|
||||
// ATA should have run
|
||||
calls := utils.NpmExecutor().NpmInstallCalls()
|
||||
assert.Equal(t, 2, len(calls), "Expected exactly 2 npm install calls")
|
||||
|
||||
// Getting the language service should not panic after
|
||||
// applying ATA changes and grabbing the latest snapshot.
|
||||
ls, err := session.GetLanguageService(context.Background(), lsproto.DocumentUri("file:///user/username/projects/project/app.js"))
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, ls != nil)
|
||||
})
|
||||
|
||||
ataDisabledCases := []struct {
|
||||
name string
|
||||
config map[string]any
|
||||
}{
|
||||
{
|
||||
name: "unified setting",
|
||||
config: map[string]any{
|
||||
"js/ts": map[string]any{
|
||||
"tsserver": map[string]any{
|
||||
"automaticTypeAcquisition": map[string]any{
|
||||
"enabled": false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "deprecated setting",
|
||||
config: map[string]any{
|
||||
"typescript": map[string]any{
|
||||
"disableAutomaticTypeAcquisition": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range ataDisabledCases {
|
||||
t.Run("ATA disabled via "+tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := map[string]any{
|
||||
"/user/username/projects/project/app.js": ``,
|
||||
"/user/username/projects/project/package.json": `{
|
||||
"name": "test",
|
||||
"dependencies": {
|
||||
"jquery": "^3.1.0"
|
||||
}
|
||||
}`,
|
||||
}
|
||||
|
||||
session, utils := projecttestutil.SetupWithTypingsInstaller(files, &projecttestutil.TypingsInstallerOptions{
|
||||
PackageToFile: map[string]string{
|
||||
"jquery": `declare const $: { x: number }`,
|
||||
},
|
||||
})
|
||||
|
||||
session.Configure(lsutil.ParseUserPreferences(tc.config))
|
||||
session.DidOpenFile(context.Background(), lsproto.DocumentUri("file:///user/username/projects/project/app.js"), 1, files["/user/username/projects/project/app.js"].(string), lsproto.LanguageKindJavaScript)
|
||||
session.WaitForBackgroundTasks()
|
||||
|
||||
calls := utils.NpmExecutor().NpmInstallCalls()
|
||||
assert.Equal(t, 0, len(calls), "Expected no npm install calls when ATA is disabled via "+tc.name)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("ATA re-enabled after being disabled triggers diagnostics refresh", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := map[string]any{
|
||||
"/user/username/projects/project/app.js": ``,
|
||||
"/user/username/projects/project/package.json": `{
|
||||
"name": "test",
|
||||
"dependencies": {
|
||||
"jquery": "^3.1.0"
|
||||
}
|
||||
}`,
|
||||
}
|
||||
|
||||
session, utils := projecttestutil.SetupWithTypingsInstaller(files, &projecttestutil.TypingsInstallerOptions{
|
||||
PackageToFile: map[string]string{
|
||||
"jquery": `declare const $: { x: number }`,
|
||||
},
|
||||
})
|
||||
|
||||
// Disable ATA
|
||||
session.Configure(lsutil.ParseUserPreferences(map[string]any{
|
||||
"js/ts": map[string]any{
|
||||
"tsserver": map[string]any{
|
||||
"automaticTypeAcquisition": map[string]any{
|
||||
"enabled": false,
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
session.DidOpenFile(context.Background(), lsproto.DocumentUri("file:///user/username/projects/project/app.js"), 1, files["/user/username/projects/project/app.js"].(string), lsproto.LanguageKindJavaScript)
|
||||
session.WaitForBackgroundTasks()
|
||||
|
||||
calls := utils.NpmExecutor().NpmInstallCalls()
|
||||
assert.Equal(t, 0, len(calls), "Expected no npm install calls when ATA is disabled")
|
||||
|
||||
baselineRefreshCount := len(utils.Client().RefreshDiagnosticsCalls())
|
||||
|
||||
// Re-enable ATA
|
||||
session.Configure(lsutil.ParseUserPreferences(map[string]any{}))
|
||||
session.WaitForBackgroundTasks()
|
||||
|
||||
refreshCount := len(utils.Client().RefreshDiagnosticsCalls())
|
||||
assert.Assert(t, refreshCount > baselineRefreshCount, "Expected RefreshDiagnostics call after ATA re-enabled")
|
||||
})
|
||||
}
|
||||
334
tools/tsgo/internal/project/ata/discovertypings.go
Normal file
334
tools/tsgo/internal/project/ata/discovertypings.go
Normal file
@@ -0,0 +1,334 @@
|
||||
package ata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/collections"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/json"
|
||||
"github.com/microsoft/typescript-go/internal/packagejson"
|
||||
"github.com/microsoft/typescript-go/internal/project/logging"
|
||||
"github.com/microsoft/typescript-go/internal/semver"
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
"github.com/microsoft/typescript-go/internal/vfs"
|
||||
"github.com/microsoft/typescript-go/internal/vfs/vfsmatch"
|
||||
)
|
||||
|
||||
func isTypingUpToDate(cachedTyping *CachedTyping, availableTypingVersions map[string]string) bool {
|
||||
useVersion, ok := availableTypingVersions["ts"+core.VersionMajorMinor()]
|
||||
if !ok {
|
||||
useVersion = availableTypingVersions["latest"]
|
||||
}
|
||||
availableVersion := semver.MustParse(useVersion)
|
||||
return availableVersion.Compare(cachedTyping.Version) <= 0
|
||||
}
|
||||
|
||||
func DiscoverTypings(
|
||||
fs vfs.FS,
|
||||
logger logging.Logger,
|
||||
typingsInfo *TypingsInfo,
|
||||
fileNames []string,
|
||||
projectRootPath string,
|
||||
packageNameToTypingLocation *collections.SyncMap[string, *CachedTyping],
|
||||
typesRegistry map[string]map[string]string,
|
||||
) (cachedTypingPaths []string, newTypingNames []string, filesToWatch []string) {
|
||||
// A typing name to typing file path mapping
|
||||
inferredTypings := map[string]string{}
|
||||
|
||||
// Only infer typings for .js and .jsx files
|
||||
fileNames = core.Filter(fileNames, func(fileName string) bool {
|
||||
return tspath.HasJSFileExtension(fileName)
|
||||
})
|
||||
|
||||
if typingsInfo.TypeAcquisition.Include != nil {
|
||||
addInferredTypings(fs, logger, inferredTypings, typingsInfo.TypeAcquisition.Include, "Explicitly included types")
|
||||
}
|
||||
exclude := typingsInfo.TypeAcquisition.Exclude
|
||||
|
||||
// Directories to search for package.json, bower.json and other typing information
|
||||
if typingsInfo.CompilerOptions.Types == nil {
|
||||
possibleSearchDirs := map[string]bool{}
|
||||
for _, fileName := range fileNames {
|
||||
possibleSearchDirs[tspath.GetDirectoryPath(fileName)] = true
|
||||
}
|
||||
possibleSearchDirs[projectRootPath] = true
|
||||
for searchDir := range possibleSearchDirs {
|
||||
filesToWatch = addTypingNamesAndGetFilesToWatch(fs, logger, inferredTypings, filesToWatch, searchDir, "bower.json", "bower_components")
|
||||
filesToWatch = addTypingNamesAndGetFilesToWatch(fs, logger, inferredTypings, filesToWatch, searchDir, "package.json", "node_modules")
|
||||
}
|
||||
}
|
||||
|
||||
if !typingsInfo.TypeAcquisition.DisableFilenameBasedTypeAcquisition.IsTrue() {
|
||||
getTypingNamesFromSourceFileNames(fs, logger, inferredTypings, fileNames)
|
||||
}
|
||||
|
||||
// add typings for unresolved imports
|
||||
var modules []string
|
||||
if typingsInfo.UnresolvedImports != nil {
|
||||
modules = make([]string, 0, typingsInfo.UnresolvedImports.Len())
|
||||
for module := range typingsInfo.UnresolvedImports.Keys() {
|
||||
modules = append(modules, core.NonRelativeModuleNameForTypingCache(module))
|
||||
}
|
||||
slices.Sort(modules)
|
||||
modules = slices.Compact(modules)
|
||||
}
|
||||
addInferredTypings(fs, logger, inferredTypings, modules, "Inferred typings from unresolved imports")
|
||||
|
||||
// Remove typings that the user has added to the exclude list
|
||||
for _, excludeTypingName := range exclude {
|
||||
delete(inferredTypings, excludeTypingName)
|
||||
logger.Log(fmt.Sprintf("ATA:: Typing for %s is in exclude list, will be ignored.", excludeTypingName))
|
||||
}
|
||||
|
||||
// Add the cached typing locations for inferred typings that are already installed
|
||||
packageNameToTypingLocation.Range(func(name string, typing *CachedTyping) bool {
|
||||
registryEntry := typesRegistry[name]
|
||||
if inferredTypings[name] == "" && registryEntry != nil && isTypingUpToDate(typing, registryEntry) {
|
||||
inferredTypings[name] = typing.TypingsLocation
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
for typing, inferred := range inferredTypings {
|
||||
if inferred != "" {
|
||||
cachedTypingPaths = append(cachedTypingPaths, inferred)
|
||||
} else {
|
||||
newTypingNames = append(newTypingNames, typing)
|
||||
}
|
||||
}
|
||||
logger.Log(fmt.Sprintf("ATA:: Finished typings discovery: cachedTypingsPaths: %v newTypingNames: %v, filesToWatch %v", cachedTypingPaths, newTypingNames, filesToWatch))
|
||||
return cachedTypingPaths, newTypingNames, filesToWatch
|
||||
}
|
||||
|
||||
func addInferredTyping(inferredTypings map[string]string, typingName string) {
|
||||
if _, ok := inferredTypings[typingName]; !ok {
|
||||
inferredTypings[typingName] = ""
|
||||
}
|
||||
}
|
||||
|
||||
func addInferredTypings(
|
||||
fs vfs.FS,
|
||||
logger logging.Logger,
|
||||
inferredTypings map[string]string,
|
||||
typingNames []string, message string,
|
||||
) {
|
||||
logger.Log(fmt.Sprintf("ATA:: %s: %v", message, typingNames))
|
||||
for _, typingName := range typingNames {
|
||||
addInferredTyping(inferredTypings, typingName)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Infer typing names from given file names. For example, the file name "jquery-min.2.3.4.js"
|
||||
* should be inferred to the 'jquery' typing name; and "angular-route.1.2.3.js" should be inferred
|
||||
* to the 'angular-route' typing name.
|
||||
* @param fileNames are the names for source files in the project
|
||||
*/
|
||||
func getTypingNamesFromSourceFileNames(
|
||||
fs vfs.FS,
|
||||
logger logging.Logger,
|
||||
inferredTypings map[string]string,
|
||||
fileNames []string,
|
||||
) {
|
||||
hasJsxFile := false
|
||||
var fromFileNames []string
|
||||
for _, fileName := range fileNames {
|
||||
hasJsxFile = hasJsxFile || tspath.FileExtensionIs(fileName, tspath.ExtensionJsx)
|
||||
inferredTypingName := tspath.RemoveFileExtension(tspath.ToFileNameLowerCase(tspath.GetBaseFileName(fileName)))
|
||||
cleanedTypingName := removeMinAndVersionNumbers(inferredTypingName)
|
||||
if typeName, ok := safeFileNameToTypeName[cleanedTypingName]; ok {
|
||||
fromFileNames = append(fromFileNames, typeName)
|
||||
}
|
||||
}
|
||||
if len(fromFileNames) > 0 {
|
||||
addInferredTypings(fs, logger, inferredTypings, fromFileNames, "Inferred typings from file names")
|
||||
}
|
||||
if hasJsxFile {
|
||||
logger.Log("ATA:: Inferred 'react' typings due to presence of '.jsx' extension")
|
||||
addInferredTyping(inferredTypings, "react")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds inferred typings from manifest/module pairs (think package.json + node_modules)
|
||||
*
|
||||
* @param projectRootPath is the path to the directory where to look for package.json, bower.json and other typing information
|
||||
* @param manifestName is the name of the manifest (package.json or bower.json)
|
||||
* @param modulesDirName is the directory name for modules (node_modules or bower_components). Should be lowercase!
|
||||
* @param filesToWatch are the files to watch for changes. We will push things into this array.
|
||||
*/
|
||||
func addTypingNamesAndGetFilesToWatch(
|
||||
fs vfs.FS,
|
||||
logger logging.Logger,
|
||||
inferredTypings map[string]string,
|
||||
filesToWatch []string,
|
||||
projectRootPath string,
|
||||
manifestName string,
|
||||
modulesDirName string,
|
||||
) []string {
|
||||
// First, we check the manifests themselves. They're not
|
||||
// _required_, but they allow us to do some filtering when dealing
|
||||
// with big flat dep directories.
|
||||
manifestPath := tspath.CombinePaths(projectRootPath, manifestName)
|
||||
var manifestTypingNames []string
|
||||
manifestContents, ok := fs.ReadFile(manifestPath)
|
||||
if ok {
|
||||
var manifest packagejson.DependencyFields
|
||||
filesToWatch = append(filesToWatch, manifestPath)
|
||||
// var manifest map[string]any
|
||||
err := json.Unmarshal([]byte(manifestContents), &manifest)
|
||||
if err == nil {
|
||||
manifestTypingNames = slices.AppendSeq(manifestTypingNames, maps.Keys(manifest.Dependencies.Value))
|
||||
manifestTypingNames = slices.AppendSeq(manifestTypingNames, maps.Keys(manifest.DevDependencies.Value))
|
||||
manifestTypingNames = slices.AppendSeq(manifestTypingNames, maps.Keys(manifest.OptionalDependencies.Value))
|
||||
manifestTypingNames = slices.AppendSeq(manifestTypingNames, maps.Keys(manifest.PeerDependencies.Value))
|
||||
addInferredTypings(fs, logger, inferredTypings, manifestTypingNames, "Typing names in '"+manifestPath+"' dependencies")
|
||||
}
|
||||
}
|
||||
|
||||
// Now we scan the directories for typing information in
|
||||
// already-installed dependencies (if present). Note that this
|
||||
// step happens regardless of whether a manifest was present,
|
||||
// which is certainly a valid configuration, if an unusual one.
|
||||
packagesFolderPath := tspath.CombinePaths(projectRootPath, modulesDirName)
|
||||
filesToWatch = append(filesToWatch, packagesFolderPath)
|
||||
if !fs.DirectoryExists(packagesFolderPath) {
|
||||
return filesToWatch
|
||||
}
|
||||
|
||||
// There's two cases we have to take into account here:
|
||||
// 1. If manifest is undefined, then we're not using a manifest.
|
||||
// That means that we should scan _all_ dependencies at the top
|
||||
// level of the modulesDir.
|
||||
// 2. If manifest is defined, then we can do some special
|
||||
// filtering to reduce the amount of scanning we need to do.
|
||||
//
|
||||
// Previous versions of this algorithm checked for a `_requiredBy`
|
||||
// field in the package.json, but that field is only present in
|
||||
// `npm@>=3 <7`.
|
||||
|
||||
// Package names that do **not** provide their own typings, so
|
||||
// we'll look them up.
|
||||
var packageNames []string
|
||||
|
||||
var dependencyManifestNames []string
|
||||
if len(manifestTypingNames) > 0 {
|
||||
// This is #1 described above.
|
||||
for _, typingName := range manifestTypingNames {
|
||||
dependencyManifestNames = append(dependencyManifestNames, tspath.CombinePaths(packagesFolderPath, typingName, manifestName))
|
||||
}
|
||||
} else {
|
||||
// And #2. Depth = 3 because scoped packages look like `node_modules/@foo/bar/package.json`
|
||||
depth := 3
|
||||
for _, manifestPath := range vfsmatch.ReadDirectory(fs, projectRootPath, packagesFolderPath, []string{tspath.ExtensionJson}, nil, nil, depth) {
|
||||
if tspath.GetBaseFileName(manifestPath) != manifestName {
|
||||
continue
|
||||
}
|
||||
|
||||
// It's ok to treat
|
||||
// `node_modules/@foo/bar/package.json` as a manifest,
|
||||
// but not `node_modules/jquery/nested/package.json`.
|
||||
// We only assume depth 3 is ok for formally scoped
|
||||
// packages. So that needs this dance here.
|
||||
|
||||
pathComponents := tspath.GetPathComponents(manifestPath, "")
|
||||
lenPathComponents := len(pathComponents)
|
||||
ch, _ := utf8.DecodeRuneInString(pathComponents[lenPathComponents-3])
|
||||
isScoped := ch == '@'
|
||||
|
||||
if isScoped && tspath.ToFileNameLowerCase(pathComponents[lenPathComponents-4]) == modulesDirName || // `node_modules/@foo/bar`
|
||||
!isScoped && tspath.ToFileNameLowerCase(pathComponents[lenPathComponents-3]) == modulesDirName { // `node_modules/foo`
|
||||
dependencyManifestNames = append(dependencyManifestNames, manifestPath)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
logger.Log(fmt.Sprintf("ATA:: Searching for typing names in %s; all files: %v", packagesFolderPath, dependencyManifestNames))
|
||||
|
||||
// Once we have the names of things to look up, we iterate over
|
||||
// and either collect their included typings, or add them to the
|
||||
// list of typings we need to look up separately.
|
||||
for _, manifestPath := range dependencyManifestNames {
|
||||
manifestContents, ok := fs.ReadFile(manifestPath)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
manifest, err := packagejson.Parse([]byte(manifestContents))
|
||||
// If the package has its own d.ts typings, those will take precedence. Otherwise the package name will be used
|
||||
// to download d.ts files from DefinitelyTyped
|
||||
if err != nil || len(manifest.Name.Value) == 0 {
|
||||
continue
|
||||
}
|
||||
ownTypes := manifest.Types.Value
|
||||
if len(ownTypes) == 0 {
|
||||
ownTypes = manifest.Typings.Value
|
||||
}
|
||||
if len(ownTypes) != 0 {
|
||||
absolutePath := tspath.GetNormalizedAbsolutePath(ownTypes, tspath.GetDirectoryPath(manifestPath))
|
||||
if fs.FileExists(absolutePath) {
|
||||
logger.Log(fmt.Sprintf("ATA:: Package '%s' provides its own types.", manifest.Name.Value))
|
||||
inferredTypings[manifest.Name.Value] = absolutePath
|
||||
} else {
|
||||
logger.Log(fmt.Sprintf("ATA:: Package '%s' provides its own types but they are missing.", manifest.Name.Value))
|
||||
}
|
||||
} else {
|
||||
packageNames = append(packageNames, manifest.Name.Value)
|
||||
}
|
||||
}
|
||||
addInferredTypings(fs, logger, inferredTypings, packageNames, " Found package names")
|
||||
return filesToWatch
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a string like "jquery-min.4.2.3" and returns "jquery"
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
func removeMinAndVersionNumbers(fileName string) string {
|
||||
// We used to use the regex /[.-]((min)|(\d+(\.\d+)*))$/ and would just .replace it twice.
|
||||
// Unfortunately, that regex has O(n^2) performance because v8 doesn't match from the end of the string.
|
||||
// Instead, we now essentially scan the filename (backwards) ourselves.
|
||||
end := len(fileName)
|
||||
for pos := end; pos > 0; {
|
||||
ch, size := utf8.DecodeLastRuneInString(fileName[:pos])
|
||||
if ch >= '0' && ch <= '9' {
|
||||
// Match a \d+ segment
|
||||
for {
|
||||
pos -= size
|
||||
ch, size = utf8.DecodeLastRuneInString(fileName[:pos])
|
||||
if pos <= 0 || ch < '0' || ch > '9' {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if pos > 4 && (ch == 'n' || ch == 'N') {
|
||||
// Looking for "min" or "min"
|
||||
// Already matched the 'n'
|
||||
pos -= size
|
||||
ch, size = utf8.DecodeLastRuneInString(fileName[:pos])
|
||||
if ch != 'i' && ch != 'I' {
|
||||
break
|
||||
}
|
||||
pos -= size
|
||||
ch, size = utf8.DecodeLastRuneInString(fileName[:pos])
|
||||
if ch != 'm' && ch != 'M' {
|
||||
break
|
||||
}
|
||||
pos -= size
|
||||
ch, size = utf8.DecodeLastRuneInString(fileName[:pos])
|
||||
} else {
|
||||
// This character is not part of either suffix pattern
|
||||
break
|
||||
}
|
||||
|
||||
if ch != '-' && ch != '.' {
|
||||
break
|
||||
}
|
||||
pos -= size
|
||||
end = pos
|
||||
}
|
||||
return fileName[0:end]
|
||||
}
|
||||
361
tools/tsgo/internal/project/ata/discovertypings_test.go
Normal file
361
tools/tsgo/internal/project/ata/discovertypings_test.go
Normal file
@@ -0,0 +1,361 @@
|
||||
package ata_test
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"testing"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/collections"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/project/ata"
|
||||
"github.com/microsoft/typescript-go/internal/project/logging"
|
||||
"github.com/microsoft/typescript-go/internal/semver"
|
||||
"github.com/microsoft/typescript-go/internal/testutil/projecttestutil"
|
||||
"github.com/microsoft/typescript-go/internal/vfs/vfstest"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestDiscoverTypings(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("should use mappings from safe list", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
logger := logging.NewLogTree("DiscoverTypings")
|
||||
files := map[string]string{
|
||||
"/home/src/projects/project/app.js": "",
|
||||
"/home/src/projects/project/jquery.js": "",
|
||||
"/home/src/projects/project/chroma.min.js": "",
|
||||
}
|
||||
fs := vfstest.FromMap(files, false /*useCaseSensitiveFileNames*/)
|
||||
cachedTypingPaths, newTypingNames, filesToWatch := ata.DiscoverTypings(
|
||||
fs,
|
||||
logger,
|
||||
&ata.TypingsInfo{
|
||||
CompilerOptions: &core.CompilerOptions{},
|
||||
TypeAcquisition: &core.TypeAcquisition{Enable: core.TSTrue},
|
||||
},
|
||||
[]string{"/home/src/projects/project/app.js", "/home/src/projects/project/jquery.js", "/home/src/projects/project/chroma.min.js"},
|
||||
"/home/src/projects/project",
|
||||
&collections.SyncMap[string, *ata.CachedTyping]{},
|
||||
map[string]map[string]string{},
|
||||
)
|
||||
assert.Assert(t, cachedTypingPaths == nil)
|
||||
assert.DeepEqual(t, collections.NewSetFromItems(newTypingNames...), collections.NewSetFromItems(
|
||||
"jquery",
|
||||
"chroma-js",
|
||||
))
|
||||
assert.DeepEqual(t, filesToWatch, []string{
|
||||
"/home/src/projects/project/bower_components",
|
||||
"/home/src/projects/project/node_modules",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("should return node for core modules", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
logger := logging.NewLogTree("DiscoverTypings")
|
||||
files := map[string]string{
|
||||
"/home/src/projects/project/app.js": "",
|
||||
}
|
||||
fs := vfstest.FromMap(files, false /*useCaseSensitiveFileNames*/)
|
||||
unresolvedImports := collections.NewSetFromItems("assert", "somename")
|
||||
cachedTypingPaths, newTypingNames, filesToWatch := ata.DiscoverTypings(
|
||||
fs,
|
||||
logger,
|
||||
&ata.TypingsInfo{
|
||||
CompilerOptions: &core.CompilerOptions{},
|
||||
TypeAcquisition: &core.TypeAcquisition{Enable: core.TSTrue},
|
||||
UnresolvedImports: unresolvedImports,
|
||||
},
|
||||
[]string{"/home/src/projects/project/app.js"},
|
||||
"/home/src/projects/project",
|
||||
&collections.SyncMap[string, *ata.CachedTyping]{},
|
||||
map[string]map[string]string{},
|
||||
)
|
||||
assert.Assert(t, cachedTypingPaths == nil)
|
||||
assert.DeepEqual(t, collections.NewSetFromItems(newTypingNames...), collections.NewSetFromItems(
|
||||
"node",
|
||||
"somename",
|
||||
))
|
||||
assert.DeepEqual(t, filesToWatch, []string{
|
||||
"/home/src/projects/project/bower_components",
|
||||
"/home/src/projects/project/node_modules",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("should use cached locations", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
logger := logging.NewLogTree("DiscoverTypings")
|
||||
files := map[string]string{
|
||||
"/home/src/projects/project/app.js": "",
|
||||
"/home/src/projects/project/node.d.ts": "",
|
||||
}
|
||||
fs := vfstest.FromMap(files, false /*useCaseSensitiveFileNames*/)
|
||||
cache := collections.SyncMap[string, *ata.CachedTyping]{}
|
||||
version := semver.MustParse("1.3.0")
|
||||
cache.Store("node", &ata.CachedTyping{
|
||||
TypingsLocation: "/home/src/projects/project/node.d.ts",
|
||||
Version: &version,
|
||||
})
|
||||
unresolvedImports := collections.NewSetFromItems("fs", "bar")
|
||||
cachedTypingPaths, newTypingNames, filesToWatch := ata.DiscoverTypings(
|
||||
fs,
|
||||
logger,
|
||||
&ata.TypingsInfo{
|
||||
CompilerOptions: &core.CompilerOptions{},
|
||||
TypeAcquisition: &core.TypeAcquisition{Enable: core.TSTrue},
|
||||
UnresolvedImports: unresolvedImports,
|
||||
},
|
||||
[]string{"/home/src/projects/project/app.js"},
|
||||
"/home/src/projects/project",
|
||||
&cache,
|
||||
map[string]map[string]string{
|
||||
"node": projecttestutil.TypesRegistryConfig(),
|
||||
},
|
||||
)
|
||||
assert.DeepEqual(t, cachedTypingPaths, []string{
|
||||
"/home/src/projects/project/node.d.ts",
|
||||
})
|
||||
assert.DeepEqual(t, collections.NewSetFromItems(newTypingNames...), collections.NewSetFromItems(
|
||||
"bar",
|
||||
))
|
||||
assert.DeepEqual(t, filesToWatch, []string{
|
||||
"/home/src/projects/project/bower_components",
|
||||
"/home/src/projects/project/node_modules",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("should gracefully handle packages that have been removed from the types-registry", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
logger := logging.NewLogTree("DiscoverTypings")
|
||||
files := map[string]string{
|
||||
"/home/src/projects/project/app.js": "",
|
||||
"/home/src/projects/project/node.d.ts": "",
|
||||
}
|
||||
fs := vfstest.FromMap(files, false /*useCaseSensitiveFileNames*/)
|
||||
cache := collections.SyncMap[string, *ata.CachedTyping]{}
|
||||
version := semver.MustParse("1.3.0")
|
||||
cache.Store("node", &ata.CachedTyping{
|
||||
TypingsLocation: "/home/src/projects/project/node.d.ts",
|
||||
Version: &version,
|
||||
})
|
||||
unresolvedImports := collections.NewSetFromItems("fs", "bar")
|
||||
cachedTypingPaths, newTypingNames, filesToWatch := ata.DiscoverTypings(
|
||||
fs,
|
||||
logger,
|
||||
&ata.TypingsInfo{
|
||||
CompilerOptions: &core.CompilerOptions{},
|
||||
TypeAcquisition: &core.TypeAcquisition{Enable: core.TSTrue},
|
||||
UnresolvedImports: unresolvedImports,
|
||||
},
|
||||
[]string{"/home/src/projects/project/app.js"},
|
||||
"/home/src/projects/project",
|
||||
&cache,
|
||||
map[string]map[string]string{},
|
||||
)
|
||||
assert.Assert(t, cachedTypingPaths == nil)
|
||||
assert.DeepEqual(t, collections.NewSetFromItems(newTypingNames...), collections.NewSetFromItems(
|
||||
"node",
|
||||
"bar",
|
||||
))
|
||||
assert.DeepEqual(t, filesToWatch, []string{
|
||||
"/home/src/projects/project/bower_components",
|
||||
"/home/src/projects/project/node_modules",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("should search only 2 levels deep", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
logger := logging.NewLogTree("DiscoverTypings")
|
||||
files := map[string]string{
|
||||
"/home/src/projects/project/app.js": "",
|
||||
"/home/src/projects/project/node_modules/a/package.json": `{ "name": "a" }`,
|
||||
"/home/src/projects/project/node_modules/a/b/package.json": `{ "name": "b" }`,
|
||||
}
|
||||
fs := vfstest.FromMap(files, false /*useCaseSensitiveFileNames*/)
|
||||
cachedTypingPaths, newTypingNames, filesToWatch := ata.DiscoverTypings(
|
||||
fs,
|
||||
logger,
|
||||
&ata.TypingsInfo{
|
||||
CompilerOptions: &core.CompilerOptions{},
|
||||
TypeAcquisition: &core.TypeAcquisition{Enable: core.TSTrue},
|
||||
},
|
||||
[]string{"/home/src/projects/project/app.js"},
|
||||
"/home/src/projects/project",
|
||||
&collections.SyncMap[string, *ata.CachedTyping]{},
|
||||
map[string]map[string]string{},
|
||||
)
|
||||
assert.Assert(t, cachedTypingPaths == nil)
|
||||
assert.DeepEqual(t, collections.NewSetFromItems(newTypingNames...), collections.NewSetFromItems(
|
||||
"a",
|
||||
))
|
||||
assert.DeepEqual(t, filesToWatch, []string{
|
||||
"/home/src/projects/project/bower_components",
|
||||
"/home/src/projects/project/node_modules",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("should support scoped packages", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
logger := logging.NewLogTree("DiscoverTypings")
|
||||
files := map[string]string{
|
||||
"/home/src/projects/project/app.js": "",
|
||||
"/home/src/projects/project/node_modules/@a/b/package.json": `{ "name": "@a/b" }`,
|
||||
}
|
||||
fs := vfstest.FromMap(files, false /*useCaseSensitiveFileNames*/)
|
||||
cachedTypingPaths, newTypingNames, filesToWatch := ata.DiscoverTypings(
|
||||
fs,
|
||||
logger,
|
||||
&ata.TypingsInfo{
|
||||
CompilerOptions: &core.CompilerOptions{},
|
||||
TypeAcquisition: &core.TypeAcquisition{Enable: core.TSTrue},
|
||||
},
|
||||
[]string{"/home/src/projects/project/app.js"},
|
||||
"/home/src/projects/project",
|
||||
&collections.SyncMap[string, *ata.CachedTyping]{},
|
||||
map[string]map[string]string{},
|
||||
)
|
||||
assert.Assert(t, cachedTypingPaths == nil)
|
||||
assert.DeepEqual(t, collections.NewSetFromItems(newTypingNames...), collections.NewSetFromItems(
|
||||
"@a/b",
|
||||
))
|
||||
assert.DeepEqual(t, filesToWatch, []string{
|
||||
"/home/src/projects/project/bower_components",
|
||||
"/home/src/projects/project/node_modules",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("should install expired typings", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
logger := logging.NewLogTree("DiscoverTypings")
|
||||
files := map[string]string{
|
||||
"/home/src/projects/project/app.js": "",
|
||||
}
|
||||
fs := vfstest.FromMap(files, false /*useCaseSensitiveFileNames*/)
|
||||
cache := collections.SyncMap[string, *ata.CachedTyping]{}
|
||||
nodeVersion := semver.MustParse("1.3.0")
|
||||
commanderVersion := semver.MustParse("1.0.0")
|
||||
cache.Store("node", &ata.CachedTyping{
|
||||
TypingsLocation: projecttestutil.TestTypingsLocation + "/node_modules/@types/node/index.d.ts",
|
||||
Version: &nodeVersion,
|
||||
})
|
||||
cache.Store("commander", &ata.CachedTyping{
|
||||
TypingsLocation: projecttestutil.TestTypingsLocation + "/node_modules/@types/commander/index.d.ts",
|
||||
Version: &commanderVersion,
|
||||
})
|
||||
unresolvedImports := collections.NewSetFromItems("http", "commander")
|
||||
cachedTypingPaths, newTypingNames, filesToWatch := ata.DiscoverTypings(
|
||||
fs,
|
||||
logger,
|
||||
&ata.TypingsInfo{
|
||||
CompilerOptions: &core.CompilerOptions{},
|
||||
TypeAcquisition: &core.TypeAcquisition{Enable: core.TSTrue},
|
||||
UnresolvedImports: unresolvedImports,
|
||||
},
|
||||
[]string{"/home/src/projects/project/app.js"},
|
||||
"/home/src/projects/project",
|
||||
&cache,
|
||||
map[string]map[string]string{
|
||||
"node": projecttestutil.TypesRegistryConfig(),
|
||||
"commander": projecttestutil.TypesRegistryConfig(),
|
||||
},
|
||||
)
|
||||
assert.DeepEqual(t, cachedTypingPaths, []string{
|
||||
"/home/src/Library/Caches/typescript/node_modules/@types/node/index.d.ts",
|
||||
})
|
||||
assert.DeepEqual(t, collections.NewSetFromItems(newTypingNames...), collections.NewSetFromItems(
|
||||
"commander",
|
||||
))
|
||||
assert.DeepEqual(t, filesToWatch, []string{
|
||||
"/home/src/projects/project/bower_components",
|
||||
"/home/src/projects/project/node_modules",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("should install expired typings with prerelease version of tsserver", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
logger := logging.NewLogTree("DiscoverTypings")
|
||||
files := map[string]string{
|
||||
"/home/src/projects/project/app.js": "",
|
||||
}
|
||||
fs := vfstest.FromMap(files, false /*useCaseSensitiveFileNames*/)
|
||||
cache := collections.SyncMap[string, *ata.CachedTyping]{}
|
||||
nodeVersion := semver.MustParse("1.0.0")
|
||||
cache.Store("node", &ata.CachedTyping{
|
||||
TypingsLocation: projecttestutil.TestTypingsLocation + "/node_modules/@types/node/index.d.ts",
|
||||
Version: &nodeVersion,
|
||||
})
|
||||
config := maps.Clone(projecttestutil.TypesRegistryConfig())
|
||||
delete(config, "ts"+core.VersionMajorMinor())
|
||||
|
||||
unresolvedImports := collections.NewSetFromItems("http")
|
||||
cachedTypingPaths, newTypingNames, filesToWatch := ata.DiscoverTypings(
|
||||
fs,
|
||||
logger,
|
||||
&ata.TypingsInfo{
|
||||
CompilerOptions: &core.CompilerOptions{},
|
||||
TypeAcquisition: &core.TypeAcquisition{Enable: core.TSTrue},
|
||||
UnresolvedImports: unresolvedImports,
|
||||
},
|
||||
[]string{"/home/src/projects/project/app.js"},
|
||||
"/home/src/projects/project",
|
||||
&cache,
|
||||
map[string]map[string]string{
|
||||
"node": config,
|
||||
},
|
||||
)
|
||||
assert.Assert(t, cachedTypingPaths == nil)
|
||||
assert.DeepEqual(t, collections.NewSetFromItems(newTypingNames...), collections.NewSetFromItems(
|
||||
"node",
|
||||
))
|
||||
assert.DeepEqual(t, filesToWatch, []string{
|
||||
"/home/src/projects/project/bower_components",
|
||||
"/home/src/projects/project/node_modules",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("prerelease typings are properly handled", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
logger := logging.NewLogTree("DiscoverTypings")
|
||||
files := map[string]string{
|
||||
"/home/src/projects/project/app.js": "",
|
||||
}
|
||||
fs := vfstest.FromMap(files, false /*useCaseSensitiveFileNames*/)
|
||||
cache := collections.SyncMap[string, *ata.CachedTyping]{}
|
||||
nodeVersion := semver.MustParse("1.3.0-next.0")
|
||||
commanderVersion := semver.MustParse("1.3.0-next.0")
|
||||
cache.Store("node", &ata.CachedTyping{
|
||||
TypingsLocation: projecttestutil.TestTypingsLocation + "/node_modules/@types/node/index.d.ts",
|
||||
Version: &nodeVersion,
|
||||
})
|
||||
cache.Store("commander", &ata.CachedTyping{
|
||||
TypingsLocation: projecttestutil.TestTypingsLocation + "/node_modules/@types/commander/index.d.ts",
|
||||
Version: &commanderVersion,
|
||||
})
|
||||
config := maps.Clone(projecttestutil.TypesRegistryConfig())
|
||||
config["ts"+core.VersionMajorMinor()] = "1.3.0-next.1"
|
||||
unresolvedImports := collections.NewSetFromItems("http", "commander")
|
||||
cachedTypingPaths, newTypingNames, filesToWatch := ata.DiscoverTypings(
|
||||
fs,
|
||||
logger,
|
||||
&ata.TypingsInfo{
|
||||
CompilerOptions: &core.CompilerOptions{},
|
||||
TypeAcquisition: &core.TypeAcquisition{Enable: core.TSTrue},
|
||||
UnresolvedImports: unresolvedImports,
|
||||
},
|
||||
[]string{"/home/src/projects/project/app.js"},
|
||||
"/home/src/projects/project",
|
||||
&cache,
|
||||
map[string]map[string]string{
|
||||
"node": config,
|
||||
"commander": projecttestutil.TypesRegistryConfig(),
|
||||
},
|
||||
)
|
||||
assert.Assert(t, cachedTypingPaths == nil)
|
||||
assert.DeepEqual(t, collections.NewSetFromItems(newTypingNames...), collections.NewSetFromItems(
|
||||
"node",
|
||||
"commander",
|
||||
))
|
||||
assert.DeepEqual(t, filesToWatch, []string{
|
||||
"/home/src/projects/project/bower_components",
|
||||
"/home/src/projects/project/node_modules",
|
||||
})
|
||||
})
|
||||
}
|
||||
523
tools/tsgo/internal/project/ata/installnpmpackages_test.go
Normal file
523
tools/tsgo/internal/project/ata/installnpmpackages_test.go
Normal file
@@ -0,0 +1,523 @@
|
||||
package ata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestInstallNpmPackages(t *testing.T) {
|
||||
t.Parallel()
|
||||
packageNames := []string{
|
||||
"@types/graphql@ts2.8",
|
||||
"@types/highlight.js@ts2.8",
|
||||
"@types/jest@ts2.8",
|
||||
"@types/mini-css-extract-plugin@ts2.8",
|
||||
"@types/mongoose@ts2.8",
|
||||
"@types/pg@ts2.8",
|
||||
"@types/webpack-bundle-analyzer@ts2.8",
|
||||
"@types/enhanced-resolve@ts2.8",
|
||||
"@types/eslint-plugin-prettier@ts2.8",
|
||||
"@types/friendly-errors-webpack-plugin@ts2.8",
|
||||
"@types/hammerjs@ts2.8",
|
||||
"@types/history@ts2.8",
|
||||
"@types/image-size@ts2.8",
|
||||
"@types/js-cookie@ts2.8",
|
||||
"@types/koa-compress@ts2.8",
|
||||
"@types/less@ts2.8",
|
||||
"@types/material-ui@ts2.8",
|
||||
"@types/mysql@ts2.8",
|
||||
"@types/nodemailer@ts2.8",
|
||||
"@types/prettier@ts2.8",
|
||||
"@types/query-string@ts2.8",
|
||||
"@types/react-places-autocomplete@ts2.8",
|
||||
"@types/react-router@ts2.8",
|
||||
"@types/react-router-config@ts2.8",
|
||||
"@types/react-select@ts2.8",
|
||||
"@types/react-transition-group@ts2.8",
|
||||
"@types/redux-form@ts2.8",
|
||||
"@types/abbrev@ts2.8",
|
||||
"@types/accepts@ts2.8",
|
||||
"@types/acorn@ts2.8",
|
||||
"@types/ansi-regex@ts2.8",
|
||||
"@types/ansi-styles@ts2.8",
|
||||
"@types/anymatch@ts2.8",
|
||||
"@types/apollo-codegen@ts2.8",
|
||||
"@types/are-we-there-yet@ts2.8",
|
||||
"@types/argparse@ts2.8",
|
||||
"@types/arr-union@ts2.8",
|
||||
"@types/array-find-index@ts2.8",
|
||||
"@types/array-uniq@ts2.8",
|
||||
"@types/array-unique@ts2.8",
|
||||
"@types/arrify@ts2.8",
|
||||
"@types/assert-plus@ts2.8",
|
||||
"@types/async@ts2.8",
|
||||
"@types/autoprefixer@ts2.8",
|
||||
"@types/aws4@ts2.8",
|
||||
"@types/babel-code-frame@ts2.8",
|
||||
"@types/babel-generator@ts2.8",
|
||||
"@types/babel-plugin-syntax-jsx@ts2.8",
|
||||
"@types/babel-template@ts2.8",
|
||||
"@types/babel-traverse@ts2.8",
|
||||
"@types/babel-types@ts2.8",
|
||||
"@types/babylon@ts2.8",
|
||||
"@types/base64-js@ts2.8",
|
||||
"@types/basic-auth@ts2.8",
|
||||
"@types/big.js@ts2.8",
|
||||
"@types/bl@ts2.8",
|
||||
"@types/bluebird@ts2.8",
|
||||
"@types/body-parser@ts2.8",
|
||||
"@types/bonjour@ts2.8",
|
||||
"@types/boom@ts2.8",
|
||||
"@types/brace-expansion@ts2.8",
|
||||
"@types/braces@ts2.8",
|
||||
"@types/brorand@ts2.8",
|
||||
"@types/browser-resolve@ts2.8",
|
||||
"@types/bson@ts2.8",
|
||||
"@types/buffer-equal@ts2.8",
|
||||
"@types/builtin-modules@ts2.8",
|
||||
"@types/bytes@ts2.8",
|
||||
"@types/callsites@ts2.8",
|
||||
"@types/camelcase@ts2.8",
|
||||
"@types/camelcase-keys@ts2.8",
|
||||
"@types/caseless@ts2.8",
|
||||
"@types/change-emitter@ts2.8",
|
||||
"@types/check-types@ts2.8",
|
||||
"@types/cheerio@ts2.8",
|
||||
"@types/chokidar@ts2.8",
|
||||
"@types/chownr@ts2.8",
|
||||
"@types/circular-json@ts2.8",
|
||||
"@types/classnames@ts2.8",
|
||||
"@types/clean-css@ts2.8",
|
||||
"@types/clone@ts2.8",
|
||||
"@types/co-body@ts2.8",
|
||||
"@types/color@ts2.8",
|
||||
"@types/color-convert@ts2.8",
|
||||
"@types/color-name@ts2.8",
|
||||
"@types/color-string@ts2.8",
|
||||
"@types/colors@ts2.8",
|
||||
"@types/combined-stream@ts2.8",
|
||||
"@types/common-tags@ts2.8",
|
||||
"@types/component-emitter@ts2.8",
|
||||
"@types/compressible@ts2.8",
|
||||
"@types/compression@ts2.8",
|
||||
"@types/concat-stream@ts2.8",
|
||||
"@types/connect-history-api-fallback@ts2.8",
|
||||
"@types/content-disposition@ts2.8",
|
||||
"@types/content-type@ts2.8",
|
||||
"@types/convert-source-map@ts2.8",
|
||||
"@types/cookie@ts2.8",
|
||||
"@types/cookie-signature@ts2.8",
|
||||
"@types/cookies@ts2.8",
|
||||
"@types/core-js@ts2.8",
|
||||
"@types/cosmiconfig@ts2.8",
|
||||
"@types/create-react-class@ts2.8",
|
||||
"@types/cross-spawn@ts2.8",
|
||||
"@types/cryptiles@ts2.8",
|
||||
"@types/css-modules-require-hook@ts2.8",
|
||||
"@types/dargs@ts2.8",
|
||||
"@types/dateformat@ts2.8",
|
||||
"@types/debug@ts2.8",
|
||||
"@types/decamelize@ts2.8",
|
||||
"@types/decompress@ts2.8",
|
||||
"@types/decompress-response@ts2.8",
|
||||
"@types/deep-equal@ts2.8",
|
||||
"@types/deep-extend@ts2.8",
|
||||
"@types/deepmerge@ts2.8",
|
||||
"@types/defined@ts2.8",
|
||||
"@types/del@ts2.8",
|
||||
"@types/depd@ts2.8",
|
||||
"@types/destroy@ts2.8",
|
||||
"@types/detect-indent@ts2.8",
|
||||
"@types/detect-newline@ts2.8",
|
||||
"@types/diff@ts2.8",
|
||||
"@types/doctrine@ts2.8",
|
||||
"@types/download@ts2.8",
|
||||
"@types/draft-js@ts2.8",
|
||||
"@types/duplexer2@ts2.8",
|
||||
"@types/duplexer3@ts2.8",
|
||||
"@types/duplexify@ts2.8",
|
||||
"@types/ejs@ts2.8",
|
||||
"@types/end-of-stream@ts2.8",
|
||||
"@types/entities@ts2.8",
|
||||
"@types/escape-html@ts2.8",
|
||||
"@types/escape-string-regexp@ts2.8",
|
||||
"@types/escodegen@ts2.8",
|
||||
"@types/eslint-scope@ts2.8",
|
||||
"@types/eslint-visitor-keys@ts2.8",
|
||||
"@types/esprima@ts2.8",
|
||||
"@types/estraverse@ts2.8",
|
||||
"@types/etag@ts2.8",
|
||||
"@types/events@ts2.8",
|
||||
"@types/execa@ts2.8",
|
||||
"@types/exenv@ts2.8",
|
||||
"@types/exit@ts2.8",
|
||||
"@types/exit-hook@ts2.8",
|
||||
"@types/expect@ts2.8",
|
||||
"@types/express@ts2.8",
|
||||
"@types/express-graphql@ts2.8",
|
||||
"@types/extend@ts2.8",
|
||||
"@types/extract-zip@ts2.8",
|
||||
"@types/fancy-log@ts2.8",
|
||||
"@types/fast-diff@ts2.8",
|
||||
"@types/fast-levenshtein@ts2.8",
|
||||
"@types/figures@ts2.8",
|
||||
"@types/file-type@ts2.8",
|
||||
"@types/filenamify@ts2.8",
|
||||
"@types/filesize@ts2.8",
|
||||
"@types/finalhandler@ts2.8",
|
||||
"@types/find-root@ts2.8",
|
||||
"@types/find-up@ts2.8",
|
||||
"@types/findup-sync@ts2.8",
|
||||
"@types/forever-agent@ts2.8",
|
||||
"@types/form-data@ts2.8",
|
||||
"@types/forwarded@ts2.8",
|
||||
"@types/fresh@ts2.8",
|
||||
"@types/from2@ts2.8",
|
||||
"@types/fs-extra@ts2.8",
|
||||
"@types/get-caller-file@ts2.8",
|
||||
"@types/get-stdin@ts2.8",
|
||||
"@types/get-stream@ts2.8",
|
||||
"@types/get-value@ts2.8",
|
||||
"@types/glob-base@ts2.8",
|
||||
"@types/glob-parent@ts2.8",
|
||||
"@types/glob-stream@ts2.8",
|
||||
"@types/globby@ts2.8",
|
||||
"@types/globule@ts2.8",
|
||||
"@types/got@ts2.8",
|
||||
"@types/graceful-fs@ts2.8",
|
||||
"@types/gulp-rename@ts2.8",
|
||||
"@types/gulp-sourcemaps@ts2.8",
|
||||
"@types/gulp-util@ts2.8",
|
||||
"@types/gzip-size@ts2.8",
|
||||
"@types/handlebars@ts2.8",
|
||||
"@types/has-ansi@ts2.8",
|
||||
"@types/hasha@ts2.8",
|
||||
"@types/he@ts2.8",
|
||||
"@types/hoek@ts2.8",
|
||||
"@types/html-entities@ts2.8",
|
||||
"@types/html-minifier@ts2.8",
|
||||
"@types/htmlparser2@ts2.8",
|
||||
"@types/http-assert@ts2.8",
|
||||
"@types/http-errors@ts2.8",
|
||||
"@types/http-proxy@ts2.8",
|
||||
"@types/http-proxy-middleware@ts2.8",
|
||||
"@types/indent-string@ts2.8",
|
||||
"@types/inflected@ts2.8",
|
||||
"@types/inherits@ts2.8",
|
||||
"@types/ini@ts2.8",
|
||||
"@types/inline-style-prefixer@ts2.8",
|
||||
"@types/inquirer@ts2.8",
|
||||
"@types/internal-ip@ts2.8",
|
||||
"@types/into-stream@ts2.8",
|
||||
"@types/invariant@ts2.8",
|
||||
"@types/ip@ts2.8",
|
||||
"@types/ip-regex@ts2.8",
|
||||
"@types/is-absolute-url@ts2.8",
|
||||
"@types/is-binary-path@ts2.8",
|
||||
"@types/is-finite@ts2.8",
|
||||
"@types/is-glob@ts2.8",
|
||||
"@types/is-my-json-valid@ts2.8",
|
||||
"@types/is-number@ts2.8",
|
||||
"@types/is-object@ts2.8",
|
||||
"@types/is-path-cwd@ts2.8",
|
||||
"@types/is-path-in-cwd@ts2.8",
|
||||
"@types/is-promise@ts2.8",
|
||||
"@types/is-scoped@ts2.8",
|
||||
"@types/is-stream@ts2.8",
|
||||
"@types/is-svg@ts2.8",
|
||||
"@types/is-url@ts2.8",
|
||||
"@types/is-windows@ts2.8",
|
||||
"@types/istanbul-lib-coverage@ts2.8",
|
||||
"@types/istanbul-lib-hook@ts2.8",
|
||||
"@types/istanbul-lib-instrument@ts2.8",
|
||||
"@types/istanbul-lib-report@ts2.8",
|
||||
"@types/istanbul-lib-source-maps@ts2.8",
|
||||
"@types/istanbul-reports@ts2.8",
|
||||
"@types/jest-diff@ts2.8",
|
||||
"@types/jest-docblock@ts2.8",
|
||||
"@types/jest-get-type@ts2.8",
|
||||
"@types/jest-matcher-utils@ts2.8",
|
||||
"@types/jest-validate@ts2.8",
|
||||
"@types/jpeg-js@ts2.8",
|
||||
"@types/js-base64@ts2.8",
|
||||
"@types/js-string-escape@ts2.8",
|
||||
"@types/js-yaml@ts2.8",
|
||||
"@types/jsbn@ts2.8",
|
||||
"@types/jsdom@ts2.8",
|
||||
"@types/jsesc@ts2.8",
|
||||
"@types/json-parse-better-errors@ts2.8",
|
||||
"@types/json-schema@ts2.8",
|
||||
"@types/json-stable-stringify@ts2.8",
|
||||
"@types/json-stringify-safe@ts2.8",
|
||||
"@types/json5@ts2.8",
|
||||
"@types/jsonfile@ts2.8",
|
||||
"@types/jsontoxml@ts2.8",
|
||||
"@types/jss@ts2.8",
|
||||
"@types/keygrip@ts2.8",
|
||||
"@types/keymirror@ts2.8",
|
||||
"@types/keyv@ts2.8",
|
||||
"@types/klaw@ts2.8",
|
||||
"@types/koa-send@ts2.8",
|
||||
"@types/leven@ts2.8",
|
||||
"@types/listr@ts2.8",
|
||||
"@types/load-json-file@ts2.8",
|
||||
"@types/loader-runner@ts2.8",
|
||||
"@types/loader-utils@ts2.8",
|
||||
"@types/locate-path@ts2.8",
|
||||
"@types/lodash-es@ts2.8",
|
||||
"@types/lodash.assign@ts2.8",
|
||||
"@types/lodash.camelcase@ts2.8",
|
||||
"@types/lodash.clonedeep@ts2.8",
|
||||
"@types/lodash.debounce@ts2.8",
|
||||
"@types/lodash.escape@ts2.8",
|
||||
"@types/lodash.flowright@ts2.8",
|
||||
"@types/lodash.get@ts2.8",
|
||||
"@types/lodash.isarguments@ts2.8",
|
||||
"@types/lodash.isarray@ts2.8",
|
||||
"@types/lodash.isequal@ts2.8",
|
||||
"@types/lodash.isobject@ts2.8",
|
||||
"@types/lodash.isstring@ts2.8",
|
||||
"@types/lodash.keys@ts2.8",
|
||||
"@types/lodash.memoize@ts2.8",
|
||||
"@types/lodash.merge@ts2.8",
|
||||
"@types/lodash.mergewith@ts2.8",
|
||||
"@types/lodash.pick@ts2.8",
|
||||
"@types/lodash.sortby@ts2.8",
|
||||
"@types/lodash.tail@ts2.8",
|
||||
"@types/lodash.template@ts2.8",
|
||||
"@types/lodash.throttle@ts2.8",
|
||||
"@types/lodash.unescape@ts2.8",
|
||||
"@types/lodash.uniq@ts2.8",
|
||||
"@types/log-symbols@ts2.8",
|
||||
"@types/log-update@ts2.8",
|
||||
"@types/loglevel@ts2.8",
|
||||
"@types/loud-rejection@ts2.8",
|
||||
"@types/lru-cache@ts2.8",
|
||||
"@types/make-dir@ts2.8",
|
||||
"@types/map-obj@ts2.8",
|
||||
"@types/media-typer@ts2.8",
|
||||
"@types/mem@ts2.8",
|
||||
"@types/mem-fs@ts2.8",
|
||||
"@types/memory-fs@ts2.8",
|
||||
"@types/meow@ts2.8",
|
||||
"@types/merge-descriptors@ts2.8",
|
||||
"@types/merge-stream@ts2.8",
|
||||
"@types/methods@ts2.8",
|
||||
"@types/micromatch@ts2.8",
|
||||
"@types/mime@ts2.8",
|
||||
"@types/mime-db@ts2.8",
|
||||
"@types/mime-types@ts2.8",
|
||||
"@types/minimatch@ts2.8",
|
||||
"@types/minimist@ts2.8",
|
||||
"@types/minipass@ts2.8",
|
||||
"@types/mkdirp@ts2.8",
|
||||
"@types/mongodb@ts2.8",
|
||||
"@types/morgan@ts2.8",
|
||||
"@types/move-concurrently@ts2.8",
|
||||
"@types/ms@ts2.8",
|
||||
"@types/msgpack-lite@ts2.8",
|
||||
"@types/multimatch@ts2.8",
|
||||
"@types/mz@ts2.8",
|
||||
"@types/negotiator@ts2.8",
|
||||
"@types/node-dir@ts2.8",
|
||||
"@types/node-fetch@ts2.8",
|
||||
"@types/node-forge@ts2.8",
|
||||
"@types/node-int64@ts2.8",
|
||||
"@types/node-ipc@ts2.8",
|
||||
"@types/node-notifier@ts2.8",
|
||||
"@types/nomnom@ts2.8",
|
||||
"@types/nopt@ts2.8",
|
||||
"@types/normalize-package-data@ts2.8",
|
||||
"@types/normalize-url@ts2.8",
|
||||
"@types/number-is-nan@ts2.8",
|
||||
"@types/object-assign@ts2.8",
|
||||
"@types/on-finished@ts2.8",
|
||||
"@types/on-headers@ts2.8",
|
||||
"@types/once@ts2.8",
|
||||
"@types/onetime@ts2.8",
|
||||
"@types/opener@ts2.8",
|
||||
"@types/opn@ts2.8",
|
||||
"@types/optimist@ts2.8",
|
||||
"@types/ora@ts2.8",
|
||||
"@types/os-homedir@ts2.8",
|
||||
"@types/os-locale@ts2.8",
|
||||
"@types/os-tmpdir@ts2.8",
|
||||
"@types/p-cancelable@ts2.8",
|
||||
"@types/p-each-series@ts2.8",
|
||||
"@types/p-event@ts2.8",
|
||||
"@types/p-lazy@ts2.8",
|
||||
"@types/p-limit@ts2.8",
|
||||
"@types/p-locate@ts2.8",
|
||||
"@types/p-map@ts2.8",
|
||||
"@types/p-map-series@ts2.8",
|
||||
"@types/p-reduce@ts2.8",
|
||||
"@types/p-timeout@ts2.8",
|
||||
"@types/p-try@ts2.8",
|
||||
"@types/pako@ts2.8",
|
||||
"@types/parse-glob@ts2.8",
|
||||
"@types/parse-json@ts2.8",
|
||||
"@types/parseurl@ts2.8",
|
||||
"@types/path-exists@ts2.8",
|
||||
"@types/path-is-absolute@ts2.8",
|
||||
"@types/path-parse@ts2.8",
|
||||
"@types/pg-pool@ts2.8",
|
||||
"@types/pg-types@ts2.8",
|
||||
"@types/pify@ts2.8",
|
||||
"@types/pixelmatch@ts2.8",
|
||||
"@types/pkg-dir@ts2.8",
|
||||
"@types/pluralize@ts2.8",
|
||||
"@types/pngjs@ts2.8",
|
||||
"@types/prelude-ls@ts2.8",
|
||||
"@types/pretty-bytes@ts2.8",
|
||||
"@types/pretty-format@ts2.8",
|
||||
"@types/progress@ts2.8",
|
||||
"@types/promise-retry@ts2.8",
|
||||
"@types/proxy-addr@ts2.8",
|
||||
"@types/pump@ts2.8",
|
||||
"@types/q@ts2.8",
|
||||
"@types/qs@ts2.8",
|
||||
"@types/range-parser@ts2.8",
|
||||
"@types/rc@ts2.8",
|
||||
"@types/rc-select@ts2.8",
|
||||
"@types/rc-slider@ts2.8",
|
||||
"@types/rc-tooltip@ts2.8",
|
||||
"@types/rc-tree@ts2.8",
|
||||
"@types/react-event-listener@ts2.8",
|
||||
"@types/react-side-effect@ts2.8",
|
||||
"@types/react-slick@ts2.8",
|
||||
"@types/read-chunk@ts2.8",
|
||||
"@types/read-pkg@ts2.8",
|
||||
"@types/read-pkg-up@ts2.8",
|
||||
"@types/recompose@ts2.8",
|
||||
"@types/recursive-readdir@ts2.8",
|
||||
"@types/relateurl@ts2.8",
|
||||
"@types/replace-ext@ts2.8",
|
||||
"@types/request@ts2.8",
|
||||
"@types/request-promise-native@ts2.8",
|
||||
"@types/require-directory@ts2.8",
|
||||
"@types/require-from-string@ts2.8",
|
||||
"@types/require-relative@ts2.8",
|
||||
"@types/resolve@ts2.8",
|
||||
"@types/resolve-from@ts2.8",
|
||||
"@types/retry@ts2.8",
|
||||
"@types/rx@ts2.8",
|
||||
"@types/rx-lite@ts2.8",
|
||||
"@types/rx-lite-aggregates@ts2.8",
|
||||
"@types/safe-regex@ts2.8",
|
||||
"@types/sane@ts2.8",
|
||||
"@types/sass-graph@ts2.8",
|
||||
"@types/sax@ts2.8",
|
||||
"@types/scriptjs@ts2.8",
|
||||
"@types/semver@ts2.8",
|
||||
"@types/send@ts2.8",
|
||||
"@types/serialize-javascript@ts2.8",
|
||||
"@types/serve-index@ts2.8",
|
||||
"@types/serve-static@ts2.8",
|
||||
"@types/set-value@ts2.8",
|
||||
"@types/shallowequal@ts2.8",
|
||||
"@types/shelljs@ts2.8",
|
||||
"@types/sockjs@ts2.8",
|
||||
"@types/sockjs-client@ts2.8",
|
||||
"@types/source-list-map@ts2.8",
|
||||
"@types/source-map-support@ts2.8",
|
||||
"@types/spdx-correct@ts2.8",
|
||||
"@types/spdy@ts2.8",
|
||||
"@types/split@ts2.8",
|
||||
"@types/sprintf@ts2.8",
|
||||
"@types/sprintf-js@ts2.8",
|
||||
"@types/sqlstring@ts2.8",
|
||||
"@types/sshpk@ts2.8",
|
||||
"@types/stack-utils@ts2.8",
|
||||
"@types/stat-mode@ts2.8",
|
||||
"@types/statuses@ts2.8",
|
||||
"@types/strict-uri-encode@ts2.8",
|
||||
"@types/string-template@ts2.8",
|
||||
"@types/strip-ansi@ts2.8",
|
||||
"@types/strip-bom@ts2.8",
|
||||
"@types/strip-json-comments@ts2.8",
|
||||
"@types/supports-color@ts2.8",
|
||||
"@types/svg2png@ts2.8",
|
||||
"@types/svgo@ts2.8",
|
||||
"@types/table@ts2.8",
|
||||
"@types/tapable@ts2.8",
|
||||
"@types/tar@ts2.8",
|
||||
"@types/temp@ts2.8",
|
||||
"@types/tempfile@ts2.8",
|
||||
"@types/through@ts2.8",
|
||||
"@types/through2@ts2.8",
|
||||
"@types/tinycolor2@ts2.8",
|
||||
"@types/tmp@ts2.8",
|
||||
"@types/to-absolute-glob@ts2.8",
|
||||
"@types/tough-cookie@ts2.8",
|
||||
"@types/trim@ts2.8",
|
||||
"@types/tryer@ts2.8",
|
||||
"@types/type-check@ts2.8",
|
||||
"@types/type-is@ts2.8",
|
||||
"@types/ua-parser-js@ts2.8",
|
||||
"@types/uglify-js@ts2.8",
|
||||
"@types/uglifyjs-webpack-plugin@ts2.8",
|
||||
"@types/underscore@ts2.8",
|
||||
"@types/uniq@ts2.8",
|
||||
"@types/uniqid@ts2.8",
|
||||
"@types/untildify@ts2.8",
|
||||
"@types/urijs@ts2.8",
|
||||
"@types/url-join@ts2.8",
|
||||
"@types/url-parse@ts2.8",
|
||||
"@types/url-regex@ts2.8",
|
||||
"@types/user-home@ts2.8",
|
||||
"@types/util-deprecate@ts2.8",
|
||||
"@types/util.promisify@ts2.8",
|
||||
"@types/utils-merge@ts2.8",
|
||||
"@types/uuid@ts2.8",
|
||||
"@types/vali-date@ts2.8",
|
||||
"@types/vary@ts2.8",
|
||||
"@types/verror@ts2.8",
|
||||
"@types/vinyl@ts2.8",
|
||||
"@types/vinyl-fs@ts2.8",
|
||||
"@types/warning@ts2.8",
|
||||
"@types/watch@ts2.8",
|
||||
"@types/watchpack@ts2.8",
|
||||
"@types/webpack-dev-middleware@ts2.8",
|
||||
"@types/webpack-sources@ts2.8",
|
||||
"@types/which@ts2.8",
|
||||
"@types/window-size@ts2.8",
|
||||
"@types/wrap-ansi@ts2.8",
|
||||
"@types/write-file-atomic@ts2.8",
|
||||
"@types/ws@ts2.8",
|
||||
"@types/xml2js@ts2.8",
|
||||
"@types/xmlbuilder@ts2.8",
|
||||
"@types/xtend@ts2.8",
|
||||
"@types/yallist@ts2.8",
|
||||
"@types/yargs@ts2.8",
|
||||
"@types/yauzl@ts2.8",
|
||||
"@types/yeoman-generator@ts2.8",
|
||||
"@types/zen-observable@ts2.8",
|
||||
"@types/react-content-loader@ts2.8",
|
||||
}
|
||||
t.Run("works when the command is too long to install all packages at once", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
var calledCount atomic.Int32
|
||||
sema := make(chan struct{}, 5)
|
||||
err := installNpmPackages(t.Context(), packageNames, sema, func(packages []string) error {
|
||||
calledCount.Add(1)
|
||||
return nil
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, int(calledCount.Load()), 2)
|
||||
})
|
||||
|
||||
t.Run("installs remaining packages when one of the partial command fails", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
var calledCount atomic.Int32
|
||||
sema := make(chan struct{}, 5)
|
||||
err := installNpmPackages(t.Context(), packageNames, sema, func(packages []string) error {
|
||||
calledCount.Add(1)
|
||||
return fmt.Errorf("failed to install packages: %v", packages)
|
||||
})
|
||||
assert.ErrorContains(t, err, "failed to install packages")
|
||||
assert.Equal(t, int(calledCount.Load()), 2)
|
||||
})
|
||||
}
|
||||
14
tools/tsgo/internal/project/ata/testmain_test.go
Normal file
14
tools/tsgo/internal/project/ata/testmain_test.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package ata_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/testutil/baseline"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
core.ApplyDebugStackLimit()
|
||||
defer baseline.Track()()
|
||||
m.Run()
|
||||
}
|
||||
505
tools/tsgo/internal/project/ata/typesmap.go
Normal file
505
tools/tsgo/internal/project/ata/typesmap.go
Normal file
@@ -0,0 +1,505 @@
|
||||
package ata
|
||||
|
||||
// type safeListEntry struct {
|
||||
// match string
|
||||
// exclude []any
|
||||
// types string
|
||||
// }
|
||||
|
||||
// var typesMap = map[string]safeListEntry{
|
||||
// "jquery": {
|
||||
// match: `jquery(-(\\.?\\d+)+)?(\\.intellisense)?(\\.min)?\\.js$`,
|
||||
// types: "jquery",
|
||||
// },
|
||||
// "WinJS": {
|
||||
// match: `^(.*\\/winjs-[.\\d]+)\\/js\\/base\\.js$`,
|
||||
// exclude: []any{"^", 1, "/.*"},
|
||||
// types: "winjs",
|
||||
// },
|
||||
// "Kendo": {
|
||||
// match: `^(.*\\/kendo(-ui)?)\\/kendo\\.all(\\.min)?\\.js$`,
|
||||
// exclude: []any{"^", 1, "/.*"},
|
||||
// types: "kendo-ui",
|
||||
// },
|
||||
// "Office Nuget": {
|
||||
// match: `^(.*\\/office\\/1)\\/excel-\\d+\\.debug\\.js$`,
|
||||
// exclude: []any{"^", 1, "/.*"},
|
||||
// types: "office",
|
||||
// },
|
||||
// "References": {
|
||||
// match: `^(.*\\/_references\\.js)$`,
|
||||
// exclude: []any{"^", 1, "$"},
|
||||
// types: "",
|
||||
// },
|
||||
// "Datatables.net": {
|
||||
// match: `^.*\\/(jquery\\.)?dataTables(\\.all)?(\\.min)?\\.js$`,
|
||||
// types: "datatables.net",
|
||||
// },
|
||||
// "Ace": {
|
||||
// match: `^(.*)\\/ace.js`,
|
||||
// exclude: []any{"^", 1, "/.*"},
|
||||
// types: "ace",
|
||||
// },
|
||||
// }
|
||||
|
||||
var safeFileNameToTypeName = map[string]string{
|
||||
"accounting": "accounting",
|
||||
"ace.js": "ace",
|
||||
"ag-grid": "ag-grid",
|
||||
"alertify": "alertify",
|
||||
"alt": "alt",
|
||||
"amcharts.js": "amcharts",
|
||||
"amplify": "amplifyjs",
|
||||
"angular": "angular",
|
||||
"angular-bootstrap-lightbox": "angular-bootstrap-lightbox",
|
||||
"angular-cookie": "angular-cookie",
|
||||
"angular-file-upload": "angular-file-upload",
|
||||
"angularfire": "angularfire",
|
||||
"angular-gettext": "angular-gettext",
|
||||
"angular-google-analytics": "angular-google-analytics",
|
||||
"angular-local-storage": "angular-local-storage",
|
||||
"angularLocalStorage": "angularLocalStorage",
|
||||
"angular-scroll": "angular-scroll",
|
||||
"angular-spinner": "angular-spinner",
|
||||
"angular-strap": "angular-strap",
|
||||
"angulartics": "angulartics",
|
||||
"angular-toastr": "angular-toastr",
|
||||
"angular-translate": "angular-translate",
|
||||
"angular-ui-router": "angular-ui-router",
|
||||
"angular-ui-tree": "angular-ui-tree",
|
||||
"angular-wizard": "angular-wizard",
|
||||
"async": "async",
|
||||
"atmosphere": "atmosphere",
|
||||
"aws-sdk": "aws-sdk",
|
||||
"aws-sdk-js": "aws-sdk",
|
||||
"axios": "axios",
|
||||
"backbone": "backbone",
|
||||
"backbone.layoutmanager": "backbone.layoutmanager",
|
||||
"backbone.paginator": "backbone.paginator",
|
||||
"backbone.radio": "backbone.radio",
|
||||
"backbone-associations": "backbone-associations",
|
||||
"backbone-relational": "backbone-relational",
|
||||
"backgrid": "backgrid",
|
||||
"Bacon": "baconjs",
|
||||
"benchmark": "benchmark",
|
||||
"blazy": "blazy",
|
||||
"bliss": "blissfuljs",
|
||||
"bluebird": "bluebird",
|
||||
"body-parser": "body-parser",
|
||||
"bootbox": "bootbox",
|
||||
"bootstrap": "bootstrap",
|
||||
"bootstrap-editable": "x-editable",
|
||||
"bootstrap-maxlength": "bootstrap-maxlength",
|
||||
"bootstrap-notify": "bootstrap-notify",
|
||||
"bootstrap-slider": "bootstrap-slider",
|
||||
"bootstrap-switch": "bootstrap-switch",
|
||||
"bowser": "bowser",
|
||||
"breeze": "breeze",
|
||||
"browserify": "browserify",
|
||||
"bson": "bson",
|
||||
"c3": "c3",
|
||||
"canvasjs": "canvasjs",
|
||||
"chai": "chai",
|
||||
"chalk": "chalk",
|
||||
"chance": "chance",
|
||||
"chartist": "chartist",
|
||||
"cheerio": "cheerio",
|
||||
"chokidar": "chokidar",
|
||||
"chosen.jquery": "chosen",
|
||||
"chroma": "chroma-js",
|
||||
"ckeditor.js": "ckeditor",
|
||||
"cli-color": "cli-color",
|
||||
"clipboard": "clipboard",
|
||||
"codemirror": "codemirror",
|
||||
"colors": "colors",
|
||||
"commander": "commander",
|
||||
"commonmark": "commonmark",
|
||||
"compression": "compression",
|
||||
"confidence": "confidence",
|
||||
"connect": "connect",
|
||||
"Control.FullScreen": "leaflet.fullscreen",
|
||||
"cookie": "cookie",
|
||||
"cookie-parser": "cookie-parser",
|
||||
"cookies": "cookies",
|
||||
"core": "core-js",
|
||||
"core-js": "core-js",
|
||||
"crossfilter": "crossfilter",
|
||||
"crossroads": "crossroads",
|
||||
"css": "css",
|
||||
"ct-ui-router-extras": "ui-router-extras",
|
||||
"d3": "d3",
|
||||
"dagre-d3": "dagre-d3",
|
||||
"dat.gui": "dat-gui",
|
||||
"debug": "debug",
|
||||
"deep-diff": "deep-diff",
|
||||
"Dexie": "dexie",
|
||||
"dialogs": "angular-dialog-service",
|
||||
"dojo.js": "dojo",
|
||||
"doT": "dot",
|
||||
"dragula": "dragula",
|
||||
"drop": "drop",
|
||||
"dropbox": "dropboxjs",
|
||||
"dropzone": "dropzone",
|
||||
"Dts Name": "Dts Name",
|
||||
"dust-core": "dustjs-linkedin",
|
||||
"easeljs": "easeljs",
|
||||
"ejs": "ejs",
|
||||
"ember": "ember",
|
||||
"envify": "envify",
|
||||
"epiceditor": "epiceditor",
|
||||
"es6-promise": "es6-promise",
|
||||
"ES6-Promise": "es6-promise",
|
||||
"es6-shim": "es6-shim",
|
||||
"expect": "expect",
|
||||
"express": "express",
|
||||
"express-session": "express-session",
|
||||
"ext-all.js": "extjs",
|
||||
"extend": "extend",
|
||||
"fabric": "fabricjs",
|
||||
"faker": "faker",
|
||||
"fastclick": "fastclick",
|
||||
"favico": "favico.js",
|
||||
"featherlight": "featherlight",
|
||||
"FileSaver": "FileSaver",
|
||||
"fingerprint": "fingerprintjs",
|
||||
"fixed-data-table": "fixed-data-table",
|
||||
"flickity.pkgd": "flickity",
|
||||
"flight": "flight",
|
||||
"flow": "flowjs",
|
||||
"Flux": "flux",
|
||||
"formly": "angular-formly",
|
||||
"foundation": "foundation",
|
||||
"fpsmeter": "fpsmeter",
|
||||
"fuse": "fuse",
|
||||
"generator": "yeoman-generator",
|
||||
"gl-matrix": "gl-matrix",
|
||||
"globalize": "globalize",
|
||||
"graceful-fs": "graceful-fs",
|
||||
"gridstack": "gridstack",
|
||||
"gulp": "gulp",
|
||||
"gulp-rename": "gulp-rename",
|
||||
"gulp-uglify": "gulp-uglify",
|
||||
"gulp-util": "gulp-util",
|
||||
"hammer": "hammerjs",
|
||||
"handlebars": "handlebars",
|
||||
"hasher": "hasher",
|
||||
"he": "he",
|
||||
"hello.all": "hellojs",
|
||||
"highcharts.js": "highcharts",
|
||||
"highlight": "highlightjs",
|
||||
"history": "history",
|
||||
"History": "history",
|
||||
"hopscotch": "hopscotch",
|
||||
"hotkeys": "angular-hotkeys",
|
||||
"html2canvas": "html2canvas",
|
||||
"humane": "humane",
|
||||
"i18next": "i18next",
|
||||
"icheck": "icheck",
|
||||
"impress": "impress",
|
||||
"incremental-dom": "incremental-dom",
|
||||
"Inquirer": "inquirer",
|
||||
"insight": "insight",
|
||||
"interact": "interactjs",
|
||||
"intercom": "intercomjs",
|
||||
"intro": "intro.js",
|
||||
"ion.rangeSlider": "ion.rangeSlider",
|
||||
"ionic": "ionic",
|
||||
"is": "is_js",
|
||||
"iscroll": "iscroll",
|
||||
"jade": "jade",
|
||||
"jasmine": "jasmine",
|
||||
"joint": "jointjs",
|
||||
"jquery": "jquery",
|
||||
"jquery.address": "jquery.address",
|
||||
"jquery.are-you-sure": "jquery.are-you-sure",
|
||||
"jquery.blockUI": "jquery.blockUI",
|
||||
"jquery.bootstrap.wizard": "jquery.bootstrap.wizard",
|
||||
"jquery.bootstrap-touchspin": "bootstrap-touchspin",
|
||||
"jquery.color": "jquery.color",
|
||||
"jquery.colorbox": "jquery.colorbox",
|
||||
"jquery.contextMenu": "jquery.contextMenu",
|
||||
"jquery.cookie": "jquery.cookie",
|
||||
"jquery.customSelect": "jquery.customSelect",
|
||||
"jquery.cycle.all": "jquery.cycle",
|
||||
"jquery.cycle2": "jquery.cycle2",
|
||||
"jquery.dataTables": "jquery.dataTables",
|
||||
"jquery.dropotron": "jquery.dropotron",
|
||||
"jquery.fancybox.pack.js": "fancybox",
|
||||
"jquery.fancytree-all": "jquery.fancytree",
|
||||
"jquery.fileupload": "jquery.fileupload",
|
||||
"jquery.flot": "flot",
|
||||
"jquery.form": "jquery.form",
|
||||
"jquery.gridster": "jquery.gridster",
|
||||
"jquery.handsontable.full": "jquery-handsontable",
|
||||
"jquery.joyride": "jquery.joyride",
|
||||
"jquery.jqGrid": "jqgrid",
|
||||
"jquery.mmenu": "jquery.mmenu",
|
||||
"jquery.mockjax": "jquery-mockjax",
|
||||
"jquery.noty": "jquery.noty",
|
||||
"jquery.payment": "jquery.payment",
|
||||
"jquery.pjax": "jquery.pjax",
|
||||
"jquery.placeholder": "jquery.placeholder",
|
||||
"jquery.qrcode": "jquery.qrcode",
|
||||
"jquery.qtip": "qtip2",
|
||||
"jquery.raty": "raty",
|
||||
"jquery.scrollTo": "jquery.scrollTo",
|
||||
"jquery.signalR": "signalr",
|
||||
"jquery.simplemodal": "jquery.simplemodal",
|
||||
"jquery.timeago": "jquery.timeago",
|
||||
"jquery.tinyscrollbar": "jquery.tinyscrollbar",
|
||||
"jquery.tipsy": "jquery.tipsy",
|
||||
"jquery.tooltipster": "tooltipster",
|
||||
"jquery.transit": "jquery.transit",
|
||||
"jquery.uniform": "jquery.uniform",
|
||||
"jquery.watch": "watch",
|
||||
"jquery-sortable": "jquery-sortable",
|
||||
"jquery-ui": "jqueryui",
|
||||
"js.cookie": "js-cookie",
|
||||
"js-data": "js-data",
|
||||
"js-data-angular": "js-data-angular",
|
||||
"js-data-http": "js-data-http",
|
||||
"jsdom": "jsdom",
|
||||
"jsnlog": "jsnlog",
|
||||
"json5": "json5",
|
||||
"jspdf": "jspdf",
|
||||
"jsrender": "jsrender",
|
||||
"js-signals": "js-signals",
|
||||
"jstorage": "jstorage",
|
||||
"jstree": "jstree",
|
||||
"js-yaml": "js-yaml",
|
||||
"jszip": "jszip",
|
||||
"katex": "katex",
|
||||
"kefir": "kefir",
|
||||
"keymaster": "keymaster",
|
||||
"keypress": "keypress",
|
||||
"kinetic": "kineticjs",
|
||||
"knockback": "knockback",
|
||||
"knockout": "knockout",
|
||||
"knockout.mapping": "knockout.mapping",
|
||||
"knockout.validation": "knockout.validation",
|
||||
"knockout-paging": "knockout-paging",
|
||||
"knockout-pre-rendered": "knockout-pre-rendered",
|
||||
"ladda": "ladda",
|
||||
"later": "later",
|
||||
"lazy": "lazy.js",
|
||||
"Leaflet.Editable": "leaflet-editable",
|
||||
"leaflet.js": "leaflet",
|
||||
"less": "less",
|
||||
"linq": "linq",
|
||||
"loading-bar": "angular-loading-bar",
|
||||
"lodash": "lodash",
|
||||
"log4javascript": "log4javascript",
|
||||
"loglevel": "loglevel",
|
||||
"lokijs": "lokijs",
|
||||
"lovefield": "lovefield",
|
||||
"lunr": "lunr",
|
||||
"lz-string": "lz-string",
|
||||
"mailcheck": "mailcheck",
|
||||
"maquette": "maquette",
|
||||
"marked": "marked",
|
||||
"math": "mathjs",
|
||||
"MathJax.js": "mathjax",
|
||||
"matter": "matter-js",
|
||||
"md5": "blueimp-md5",
|
||||
"md5.js": "crypto-js",
|
||||
"messenger": "messenger",
|
||||
"method-override": "method-override",
|
||||
"minimatch": "minimatch",
|
||||
"minimist": "minimist",
|
||||
"mithril": "mithril",
|
||||
"mobile-detect": "mobile-detect",
|
||||
"mocha": "mocha",
|
||||
"mock-ajax": "jasmine-ajax",
|
||||
"modernizr": "modernizr",
|
||||
"Modernizr": "Modernizr",
|
||||
"moment": "moment",
|
||||
"moment-range": "moment-range",
|
||||
"moment-timezone": "moment-timezone",
|
||||
"mongoose": "mongoose",
|
||||
"morgan": "morgan",
|
||||
"mousetrap": "mousetrap",
|
||||
"ms": "ms",
|
||||
"mustache": "mustache",
|
||||
"native.history": "history",
|
||||
"nconf": "nconf",
|
||||
"ncp": "ncp",
|
||||
"nedb": "nedb",
|
||||
"ng-cordova": "ng-cordova",
|
||||
"ngDialog": "ng-dialog",
|
||||
"ng-flow-standalone": "ng-flow",
|
||||
"ng-grid": "ng-grid",
|
||||
"ng-i18next": "ng-i18next",
|
||||
"ng-table": "ng-table",
|
||||
"node_redis": "redis",
|
||||
"node-clone": "clone",
|
||||
"node-fs-extra": "fs-extra",
|
||||
"node-glob": "glob",
|
||||
"Nodemailer": "nodemailer",
|
||||
"node-mime": "mime",
|
||||
"node-mkdirp": "mkdirp",
|
||||
"node-mongodb-native": "mongodb",
|
||||
"node-mysql": "mysql",
|
||||
"node-open": "open",
|
||||
"node-optimist": "optimist",
|
||||
"node-progress": "progress",
|
||||
"node-semver": "semver",
|
||||
"node-tar": "tar",
|
||||
"node-uuid": "node-uuid",
|
||||
"node-xml2js": "xml2js",
|
||||
"nopt": "nopt",
|
||||
"notify": "notify",
|
||||
"nouislider": "nouislider",
|
||||
"npm": "npm",
|
||||
"nprogress": "nprogress",
|
||||
"numbro": "numbro",
|
||||
"numeral": "numeraljs",
|
||||
"nunjucks": "nunjucks",
|
||||
"nv.d3": "nvd3",
|
||||
"object-assign": "object-assign",
|
||||
"oboe-browser": "oboe",
|
||||
"office": "office-js",
|
||||
"offline": "offline-js",
|
||||
"onsenui": "onsenui",
|
||||
"OpenLayers.js": "openlayers",
|
||||
"openpgp": "openpgp",
|
||||
"p2": "p2",
|
||||
"packery.pkgd": "packery",
|
||||
"page": "page",
|
||||
"pako": "pako",
|
||||
"papaparse": "papaparse",
|
||||
"passport": "passport",
|
||||
"passport-local": "passport-local",
|
||||
"path": "pathjs",
|
||||
"pdfkit": "pdfkit",
|
||||
"peer": "peerjs",
|
||||
"peg": "pegjs",
|
||||
"photoswipe": "photoswipe",
|
||||
"picker.js": "pickadate",
|
||||
"pikaday": "pikaday",
|
||||
"pixi": "pixi.js",
|
||||
"platform": "platform",
|
||||
"Please": "pleasejs",
|
||||
"plottable": "plottable",
|
||||
"polymer": "polymer",
|
||||
"postal": "postal",
|
||||
"preloadjs": "preloadjs",
|
||||
"progress": "progress",
|
||||
"purify": "dompurify",
|
||||
"purl": "purl",
|
||||
"q": "q",
|
||||
"qs": "qs",
|
||||
"qunit": "qunit",
|
||||
"ractive": "ractive",
|
||||
"rangy-core": "rangy",
|
||||
"raphael": "raphael",
|
||||
"raven": "ravenjs",
|
||||
"react": "react",
|
||||
"react-bootstrap": "react-bootstrap",
|
||||
"react-intl": "react-intl",
|
||||
"react-redux": "react-redux",
|
||||
"ReactRouter": "react-router",
|
||||
"ready": "domready",
|
||||
"redux": "redux",
|
||||
"request": "request",
|
||||
"require": "require",
|
||||
"restangular": "restangular",
|
||||
"reveal": "reveal",
|
||||
"rickshaw": "rickshaw",
|
||||
"rimraf": "rimraf",
|
||||
"rivets": "rivets",
|
||||
"rx": "rx",
|
||||
"rx.angular": "rx-angular",
|
||||
"sammy": "sammyjs",
|
||||
"SAT": "sat",
|
||||
"sax-js": "sax",
|
||||
"screenfull": "screenfull",
|
||||
"seedrandom": "seedrandom",
|
||||
"select2": "select2",
|
||||
"selectize": "selectize",
|
||||
"serve-favicon": "serve-favicon",
|
||||
"serve-static": "serve-static",
|
||||
"shelljs": "shelljs",
|
||||
"should": "should",
|
||||
"showdown": "showdown",
|
||||
"sigma": "sigmajs",
|
||||
"signature_pad": "signature_pad",
|
||||
"sinon": "sinon",
|
||||
"sjcl": "sjcl",
|
||||
"slick": "slick-carousel",
|
||||
"smoothie": "smoothie",
|
||||
"socket.io": "socket.io",
|
||||
"socket.io-client": "socket.io-client",
|
||||
"sockjs": "sockjs-client",
|
||||
"sortable": "angular-ui-sortable",
|
||||
"soundjs": "soundjs",
|
||||
"source-map": "source-map",
|
||||
"spectrum": "spectrum",
|
||||
"spin": "spin",
|
||||
"sprintf": "sprintf",
|
||||
"stampit": "stampit",
|
||||
"state-machine": "state-machine",
|
||||
"Stats": "stats",
|
||||
"store": "storejs",
|
||||
"string": "string",
|
||||
"string_score": "string_score",
|
||||
"strophe": "strophe",
|
||||
"stylus": "stylus",
|
||||
"sugar": "sugar",
|
||||
"superagent": "superagent",
|
||||
"svg": "svgjs",
|
||||
"svg-injector": "svg-injector",
|
||||
"swfobject": "swfobject",
|
||||
"swig": "swig",
|
||||
"swipe": "swipe",
|
||||
"swiper": "swiper",
|
||||
"system.js": "systemjs",
|
||||
"tether": "tether",
|
||||
"three": "threejs",
|
||||
"through": "through",
|
||||
"through2": "through2",
|
||||
"timeline": "timelinejs",
|
||||
"tinycolor": "tinycolor",
|
||||
"tmhDynamicLocale": "angular-dynamic-locale",
|
||||
"toaster": "angularjs-toaster",
|
||||
"toastr": "toastr",
|
||||
"tracking": "tracking",
|
||||
"trunk8": "trunk8",
|
||||
"turf": "turf",
|
||||
"tweenjs": "tweenjs",
|
||||
"TweenMax": "gsap",
|
||||
"twig": "twig",
|
||||
"twix": "twix",
|
||||
"typeahead.bundle": "typeahead",
|
||||
"typescript": "typescript",
|
||||
"ui": "winjs",
|
||||
"ui-bootstrap-tpls": "angular-ui-bootstrap",
|
||||
"ui-grid": "ui-grid",
|
||||
"uikit": "uikit",
|
||||
"underscore": "underscore",
|
||||
"underscore.string": "underscore.string",
|
||||
"update-notifier": "update-notifier",
|
||||
"url": "jsurl",
|
||||
"UUID": "uuid",
|
||||
"validator": "validator",
|
||||
"vega": "vega",
|
||||
"vex": "vex-js",
|
||||
"video": "videojs",
|
||||
"vue": "vue",
|
||||
"vue-router": "vue-router",
|
||||
"webtorrent": "webtorrent",
|
||||
"when": "when",
|
||||
"winston": "winston",
|
||||
"wrench-js": "wrench",
|
||||
"ws": "ws",
|
||||
"xlsx": "xlsx",
|
||||
"xml2json": "x2js",
|
||||
"xmlbuilder-js": "xmlbuilder",
|
||||
"xregexp": "xregexp",
|
||||
"yargs": "yargs",
|
||||
"yosay": "yosay",
|
||||
"yui": "yui",
|
||||
"yui3": "yui",
|
||||
"zepto": "zepto",
|
||||
"ZeroClipboard": "zeroclipboard",
|
||||
"ZSchema-browser": "z-schema",
|
||||
}
|
||||
98
tools/tsgo/internal/project/ata/validatepackagename.go
Normal file
98
tools/tsgo/internal/project/ata/validatepackagename.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package ata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type NameValidationResult int
|
||||
|
||||
const (
|
||||
NameOk NameValidationResult = iota
|
||||
EmptyName
|
||||
NameTooLong
|
||||
NameStartsWithDot
|
||||
NameStartsWithUnderscore
|
||||
NameContainsNonURISafeCharacters
|
||||
)
|
||||
|
||||
const maxPackageNameLength = 214
|
||||
|
||||
/**
|
||||
* Validates package name using rules defined at https://docs.npmjs.com/files/package.json
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
func ValidatePackageName(packageName string) (result NameValidationResult, name string, isScopeName bool) {
|
||||
return validatePackageNameWorker(packageName /*supportScopedPackage*/, true)
|
||||
}
|
||||
|
||||
func validatePackageNameWorker(packageName string, supportScopedPackage bool) (result NameValidationResult, name string, isScopeName bool) {
|
||||
packageNameLen := len(packageName)
|
||||
if packageNameLen == 0 {
|
||||
return EmptyName, "", false
|
||||
}
|
||||
if packageNameLen > maxPackageNameLength {
|
||||
return NameTooLong, "", false
|
||||
}
|
||||
firstChar, _ := utf8.DecodeRuneInString(packageName)
|
||||
if firstChar == '.' {
|
||||
return NameStartsWithDot, "", false
|
||||
}
|
||||
if firstChar == '_' {
|
||||
return NameStartsWithUnderscore, "", false
|
||||
}
|
||||
// check if name is scope package like: starts with @ and has one '/' in the middle
|
||||
// scoped packages are not currently supported
|
||||
if supportScopedPackage {
|
||||
if withoutScope, found := strings.CutPrefix(packageName, "@"); found {
|
||||
scope, scopedPackageName, found := strings.Cut(withoutScope, "/")
|
||||
if found && len(scope) > 0 && len(scopedPackageName) > 0 && !strings.Contains(scopedPackageName, "/") {
|
||||
scopeResult, _, _ := validatePackageNameWorker(scope /*supportScopedPackage*/, false)
|
||||
if scopeResult != NameOk {
|
||||
return scopeResult, scope, true
|
||||
}
|
||||
packageResult, _, _ := validatePackageNameWorker(scopedPackageName /*supportScopedPackage*/, false)
|
||||
if packageResult != NameOk {
|
||||
return packageResult, scopedPackageName, false
|
||||
}
|
||||
return NameOk, "", false
|
||||
}
|
||||
}
|
||||
}
|
||||
if url.QueryEscape(packageName) != packageName {
|
||||
return NameContainsNonURISafeCharacters, "", false
|
||||
}
|
||||
return NameOk, "", false
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
func renderPackageNameValidationFailure(typing string, result NameValidationResult, name string, isScopeName bool) string {
|
||||
var kind string
|
||||
if isScopeName {
|
||||
kind = "Scope"
|
||||
} else {
|
||||
kind = "Package"
|
||||
}
|
||||
if name == "" {
|
||||
name = typing
|
||||
}
|
||||
switch result {
|
||||
case EmptyName:
|
||||
return fmt.Sprintf("'%s':: %s name '%s' cannot be empty", typing, kind, name)
|
||||
case NameTooLong:
|
||||
return fmt.Sprintf("'%s':: %s name '%s' should be less than %d characters", typing, kind, name, maxPackageNameLength)
|
||||
case NameStartsWithDot:
|
||||
return fmt.Sprintf("'%s':: %s name '%s' cannot start with '.'", typing, kind, name)
|
||||
case NameStartsWithUnderscore:
|
||||
return fmt.Sprintf("'%s':: %s name '%s' cannot start with '_'", typing, kind, name)
|
||||
case NameContainsNonURISafeCharacters:
|
||||
return fmt.Sprintf("'%s':: %s name '%s' contains non URI safe characters", typing, kind, name)
|
||||
case NameOk:
|
||||
panic("Unexpected Ok result")
|
||||
default:
|
||||
panic("Unknown package name validation result")
|
||||
}
|
||||
}
|
||||
109
tools/tsgo/internal/project/ata/validatepackagename_test.go
Normal file
109
tools/tsgo/internal/project/ata/validatepackagename_test.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package ata_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/project/ata"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestValidatePackageName(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("name cannot be too long", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
var packageName strings.Builder
|
||||
packageName.WriteString("a")
|
||||
for range 8 {
|
||||
packageName.WriteString(packageName.String())
|
||||
}
|
||||
status, _, _ := ata.ValidatePackageName(packageName.String())
|
||||
assert.Equal(t, status, ata.NameTooLong)
|
||||
})
|
||||
t.Run("package name cannot start with dot", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
status, _, _ := ata.ValidatePackageName(".foo")
|
||||
assert.Equal(t, status, ata.NameStartsWithDot)
|
||||
})
|
||||
t.Run("package name cannot start with underscore", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
status, _, _ := ata.ValidatePackageName("_foo")
|
||||
assert.Equal(t, status, ata.NameStartsWithUnderscore)
|
||||
})
|
||||
t.Run("package non URI safe characters are not supported", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
status, _, _ := ata.ValidatePackageName(" scope ")
|
||||
assert.Equal(t, status, ata.NameContainsNonURISafeCharacters)
|
||||
status, _, _ = ata.ValidatePackageName("; say ‘Hello from TypeScript!’ #")
|
||||
assert.Equal(t, status, ata.NameContainsNonURISafeCharacters)
|
||||
status, _, _ = ata.ValidatePackageName("a/b/c")
|
||||
assert.Equal(t, status, ata.NameContainsNonURISafeCharacters)
|
||||
})
|
||||
t.Run("scoped package name is supported", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
status, _, _ := ata.ValidatePackageName("@scope/bar")
|
||||
assert.Equal(t, status, ata.NameOk)
|
||||
})
|
||||
t.Run("scoped name in scoped package name cannot start with dot", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
status, name, isScopeName := ata.ValidatePackageName("@.scope/bar")
|
||||
assert.Equal(t, status, ata.NameStartsWithDot)
|
||||
assert.Equal(t, name, ".scope")
|
||||
assert.Equal(t, isScopeName, true)
|
||||
status, name, isScopeName = ata.ValidatePackageName("@.scope/.bar")
|
||||
assert.Equal(t, status, ata.NameStartsWithDot)
|
||||
assert.Equal(t, name, ".scope")
|
||||
assert.Equal(t, isScopeName, true)
|
||||
})
|
||||
t.Run("scoped name in scoped package name cannot start with dot", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
status, name, isScopeName := ata.ValidatePackageName("@_scope/bar")
|
||||
assert.Equal(t, status, ata.NameStartsWithUnderscore)
|
||||
assert.Equal(t, name, "_scope")
|
||||
assert.Equal(t, isScopeName, true)
|
||||
status, name, isScopeName = ata.ValidatePackageName("@_scope/_bar")
|
||||
assert.Equal(t, status, ata.NameStartsWithUnderscore)
|
||||
assert.Equal(t, name, "_scope")
|
||||
assert.Equal(t, isScopeName, true)
|
||||
})
|
||||
t.Run("scope name in scoped package name with non URI safe characters are not supported", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
status, name, isScopeName := ata.ValidatePackageName("@ scope /bar")
|
||||
assert.Equal(t, status, ata.NameContainsNonURISafeCharacters)
|
||||
assert.Equal(t, name, " scope ")
|
||||
assert.Equal(t, isScopeName, true)
|
||||
status, name, isScopeName = ata.ValidatePackageName("@; say ‘Hello from TypeScript!’ #/bar")
|
||||
assert.Equal(t, status, ata.NameContainsNonURISafeCharacters)
|
||||
assert.Equal(t, name, "; say ‘Hello from TypeScript!’ #")
|
||||
assert.Equal(t, isScopeName, true)
|
||||
status, name, isScopeName = ata.ValidatePackageName("@ scope / bar ")
|
||||
assert.Equal(t, status, ata.NameContainsNonURISafeCharacters)
|
||||
assert.Equal(t, name, " scope ")
|
||||
assert.Equal(t, isScopeName, true)
|
||||
})
|
||||
t.Run("package name in scoped package name cannot start with dot", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
status, name, isScopeName := ata.ValidatePackageName("@scope/.bar")
|
||||
assert.Equal(t, status, ata.NameStartsWithDot)
|
||||
assert.Equal(t, name, ".bar")
|
||||
assert.Equal(t, isScopeName, false)
|
||||
})
|
||||
t.Run("package name in scoped package name cannot start with underscore", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
status, name, isScopeName := ata.ValidatePackageName("@scope/_bar")
|
||||
assert.Equal(t, status, ata.NameStartsWithUnderscore)
|
||||
assert.Equal(t, name, "_bar")
|
||||
assert.Equal(t, isScopeName, false)
|
||||
})
|
||||
t.Run("package name in scoped package name with non URI safe characters are not supported", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
status, name, isScopeName := ata.ValidatePackageName("@scope/ bar ")
|
||||
assert.Equal(t, status, ata.NameContainsNonURISafeCharacters)
|
||||
assert.Equal(t, name, " bar ")
|
||||
assert.Equal(t, isScopeName, false)
|
||||
status, name, isScopeName = ata.ValidatePackageName("@scope/; say ‘Hello from TypeScript!’ #")
|
||||
assert.Equal(t, status, ata.NameContainsNonURISafeCharacters)
|
||||
assert.Equal(t, name, "; say ‘Hello from TypeScript!’ #")
|
||||
assert.Equal(t, isScopeName, false)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user