vendor tsgo

This commit is contained in:
2026-07-09 16:50:43 -04:00
parent c06ea2e5a4
commit 98978e4930
5804 changed files with 1556156 additions and 101 deletions

View 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{}
}

View 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")
})
}

View 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]
}

View 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",
})
})
}

View 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)
})
}

View 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()
}

View 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",
}

View 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")
}
}

View 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)
})
}