vendor tsgo
This commit is contained in:
389
tools/tsgo/internal/tsoptions/showconfig.go
Normal file
389
tools/tsgo/internal/tsoptions/showconfig.go
Normal file
@@ -0,0 +1,389 @@
|
||||
package tsoptions
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/collections"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/debug"
|
||||
"github.com/microsoft/typescript-go/internal/diagnostics"
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
)
|
||||
|
||||
// computeFn wraps a typed getter method so it can be stored in an impliedOption's
|
||||
// compute field (which has type func(*core.CompilerOptions) any).
|
||||
func computeFn[T any](fn func(*core.CompilerOptions) T) func(*core.CompilerOptions) any {
|
||||
return func(opts *core.CompilerOptions) any {
|
||||
return fn(opts)
|
||||
}
|
||||
}
|
||||
|
||||
// impliedOption describes a compiler option whose effective value can be derived from
|
||||
// other options. This mirrors TypeScript's computedOptions concept used in convertToTSConfig.
|
||||
type impliedOption struct {
|
||||
// name is the Go struct field name of the CompilerOptions field (e.g., "Module").
|
||||
name string
|
||||
// dependencies lists the Go struct field names that this option depends on.
|
||||
dependencies []string
|
||||
// compute returns the effective value of this option given compiler options.
|
||||
compute func(opts *core.CompilerOptions) any
|
||||
}
|
||||
|
||||
// impliedOptions lists the compiler options that may be implied by other options,
|
||||
// mirroring TypeScript's computedOptions used in convertToTSConfig.
|
||||
// Each compute function delegates directly to an existing core.CompilerOptions getter.
|
||||
var impliedOptions = []impliedOption{
|
||||
{name: "Module", dependencies: []string{"Target"}, compute: computeFn((*core.CompilerOptions).GetEmitModuleKind)},
|
||||
{name: "ModuleResolution", dependencies: []string{"Module", "Target"}, compute: computeFn((*core.CompilerOptions).GetModuleResolutionKind)},
|
||||
{name: "ModuleDetection", dependencies: []string{"Module", "Target"}, compute: computeFn((*core.CompilerOptions).GetEmitModuleDetectionKind)},
|
||||
{name: "IsolatedModules", dependencies: []string{"VerbatimModuleSyntax"}, compute: computeFn((*core.CompilerOptions).GetIsolatedModules)},
|
||||
{name: "PreserveConstEnums", dependencies: []string{"IsolatedModules", "VerbatimModuleSyntax"}, compute: computeFn((*core.CompilerOptions).ShouldPreserveConstEnums)},
|
||||
{name: "Declaration", dependencies: []string{"Composite"}, compute: computeFn((*core.CompilerOptions).GetEmitDeclarations)},
|
||||
{name: "DeclarationMap", dependencies: []string{"Declaration", "Composite"}, compute: computeFn((*core.CompilerOptions).GetAreDeclarationMapsEnabled)},
|
||||
{name: "Incremental", dependencies: []string{"Composite"}, compute: computeFn((*core.CompilerOptions).IsIncremental)},
|
||||
{name: "UseDefineForClassFields", dependencies: []string{"Target", "Module"}, compute: computeFn((*core.CompilerOptions).GetUseDefineForClassFields)},
|
||||
{name: "ResolvePackageJsonExports", dependencies: []string{"ModuleResolution", "Module", "Target"}, compute: computeFn((*core.CompilerOptions).GetResolvePackageJsonExports)},
|
||||
{name: "ResolvePackageJsonImports", dependencies: []string{"ModuleResolution", "ResolvePackageJsonExports", "Module", "Target"}, compute: computeFn((*core.CompilerOptions).GetResolvePackageJsonImports)},
|
||||
{name: "ResolveJsonModule", dependencies: []string{"ModuleResolution", "Module", "Target"}, compute: computeFn((*core.CompilerOptions).GetResolveJsonModule)},
|
||||
{name: "AllowJs", dependencies: []string{"CheckJs"}, compute: computeFn((*core.CompilerOptions).GetAllowJS)},
|
||||
{name: "AllowImportingTsExtensions", dependencies: []string{"RewriteRelativeImportExtensions"}, compute: computeFn((*core.CompilerOptions).GetAllowImportingTsExtensions)},
|
||||
}
|
||||
|
||||
// TSConfig represents the output structure for --showConfig
|
||||
type TSConfig struct {
|
||||
CompilerOptions *collections.OrderedMap[string, any] `json:"compilerOptions"`
|
||||
References []any `json:"references,omitzero"`
|
||||
Files []string `json:"files,omitzero"`
|
||||
Include []string `json:"include,omitzero"`
|
||||
Exclude []string `json:"exclude,omitzero"`
|
||||
CompileOnSave *bool `json:"compileOnSave,omitzero"`
|
||||
}
|
||||
|
||||
// ConvertToTSConfig generates a complete tsconfig representation for --showConfig output,
|
||||
// matching the behavior of TypeScript's convertToTSConfig function.
|
||||
func ConvertToTSConfig(configParseResult *ParsedCommandLine, configFileName string) *TSConfig {
|
||||
if configFileName == "" {
|
||||
configFileName = "tsconfig.json"
|
||||
}
|
||||
normalizedConfigPath := tspath.GetNormalizedAbsolutePath(configFileName, configParseResult.GetCurrentDirectory())
|
||||
comparePathsOptions := tspath.ComparePathsOptions{
|
||||
CurrentDirectory: configParseResult.GetCurrentDirectory(),
|
||||
UseCaseSensitiveFileNames: configParseResult.UseCaseSensitiveFileNames(),
|
||||
}
|
||||
|
||||
// Build the list of all resolved files as relative paths from the config file.
|
||||
var files []string
|
||||
for _, f := range configParseResult.FileNames() {
|
||||
normalizedFilePath := tspath.GetNormalizedAbsolutePath(f, configParseResult.GetCurrentDirectory())
|
||||
relativePath := tspath.GetRelativePathFromFile(normalizedConfigPath, normalizedFilePath, comparePathsOptions)
|
||||
files = append(files, relativePath)
|
||||
}
|
||||
|
||||
// Serialize compiler options
|
||||
optionMap := serializeCompilerOptions(configParseResult.CompilerOptions(), normalizedConfigPath, comparePathsOptions)
|
||||
|
||||
// Remove command-line-only options from the output
|
||||
for _, name := range []string{
|
||||
"showConfig", "configFile", "configFilePath", "help", "init",
|
||||
"listFilesOnly", "listEmittedFiles", "project", "build", "version",
|
||||
} {
|
||||
optionMap.Delete(name)
|
||||
}
|
||||
|
||||
// Add implied compiler options (options that are derived from explicitly set options,
|
||||
// such as moduleResolution implied by module, or useDefineForClassFields implied by target).
|
||||
// This mirrors TypeScript's convertToTSConfig computedOptions logic.
|
||||
addImpliedOptions(optionMap, configParseResult.CompilerOptions(), normalizedConfigPath, comparePathsOptions)
|
||||
|
||||
config := &TSConfig{
|
||||
CompilerOptions: optionMap,
|
||||
}
|
||||
|
||||
// Add references
|
||||
if refs := configParseResult.ProjectReferences(); len(refs) > 0 {
|
||||
var references []any
|
||||
for _, r := range refs {
|
||||
ref := &collections.OrderedMap[string, any]{}
|
||||
ref.Set("path", r.OriginalPath)
|
||||
if r.Circular {
|
||||
ref.Set("circular", true)
|
||||
}
|
||||
references = append(references, ref)
|
||||
}
|
||||
config.References = references
|
||||
}
|
||||
|
||||
// Add files
|
||||
if len(files) > 0 {
|
||||
config.Files = files
|
||||
}
|
||||
|
||||
// Add include/exclude from configFileSpecs
|
||||
if configParseResult.ConfigFile != nil && configParseResult.ConfigFile.configFileSpecs != nil {
|
||||
specs := configParseResult.ConfigFile.configFileSpecs
|
||||
include := filterSameAsDefaultInclude(specs.validatedIncludeSpecs)
|
||||
if len(include) > 0 {
|
||||
config.Include = include
|
||||
}
|
||||
config.Exclude = specs.validatedExcludeSpecs
|
||||
}
|
||||
|
||||
// Add compileOnSave
|
||||
if configParseResult.CompileOnSave != nil && *configParseResult.CompileOnSave {
|
||||
t := true
|
||||
config.CompileOnSave = &t
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// filterSameAsDefaultInclude returns nil if specs is the default include spec ["**/*"]
|
||||
func filterSameAsDefaultInclude(specs []string) []string {
|
||||
if len(specs) == 0 {
|
||||
return nil
|
||||
}
|
||||
if len(specs) == 1 && specs[0] == defaultIncludeSpec {
|
||||
return nil
|
||||
}
|
||||
return specs
|
||||
}
|
||||
|
||||
// getNameOfCompilerOptionValue returns the string key for a given enum value by
|
||||
// searching the option's enum map.
|
||||
func getNameOfCompilerOptionValue(value any, enumMap *collections.OrderedMap[string, any]) string {
|
||||
for k, v := range enumMap.Entries() {
|
||||
if v == value {
|
||||
return k
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// serializeCompilerOptions converts CompilerOptions to an ordered map with
|
||||
// string names as keys and serialized values (enums as strings, paths as
|
||||
// relative paths, etc.) matching the output of tsc --showConfig.
|
||||
func serializeCompilerOptions(options *core.CompilerOptions, configFilePath string, comparePathsOptions tspath.ComparePathsOptions) *collections.OrderedMap[string, any] {
|
||||
result := collections.NewOrderedMapWithSizeHint[string, any](32)
|
||||
configDir := tspath.GetDirectoryPath(configFilePath)
|
||||
|
||||
optionsValue := reflect.ValueOf(options).Elem()
|
||||
optionsTypeInfo := reflect.TypeFor[core.CompilerOptions]()
|
||||
|
||||
for i := range optionsValue.NumField() {
|
||||
field := optionsTypeInfo.Field(i)
|
||||
if !field.IsExported() {
|
||||
continue
|
||||
}
|
||||
|
||||
optionDecl := CommandLineCompilerOptionsMap.Get(field.Name)
|
||||
if optionDecl == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip command-line-only and output formatting options
|
||||
if optionDecl.Category == diagnostics.Command_line_Options || optionDecl.Category == diagnostics.Output_Formatting {
|
||||
continue
|
||||
}
|
||||
|
||||
fieldValue := optionsValue.Field(i)
|
||||
|
||||
// Skip zero values (unset options)
|
||||
if fieldValue.IsZero() {
|
||||
continue
|
||||
}
|
||||
|
||||
name := optionDecl.Name
|
||||
value := fieldValue.Interface()
|
||||
|
||||
enumMap := optionDecl.EnumMap()
|
||||
if enumMap != nil {
|
||||
// Enum option - convert numeric value to string name
|
||||
serialized := serializeEnumValue(value, enumMap)
|
||||
if serialized != "" {
|
||||
result.Set(name, serialized)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
switch optionDecl.Kind {
|
||||
case CommandLineOptionTypeListOrElement:
|
||||
debug.Assert(false, "listOrElement option should not reach serialization")
|
||||
case CommandLineOptionTypeList:
|
||||
elem := optionDecl.Elements()
|
||||
if elem != nil && elem.IsFilePath {
|
||||
// List of file paths - make relative
|
||||
if strs, ok := value.([]string); ok {
|
||||
relPaths := make([]string, len(strs))
|
||||
for j, s := range strs {
|
||||
absPath := tspath.GetNormalizedAbsolutePath(s, configDir)
|
||||
relPaths[j] = tspath.GetRelativePathFromFile(configFilePath, absPath, comparePathsOptions)
|
||||
}
|
||||
result.Set(name, relPaths)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if elem != nil && elem.EnumMap() != nil {
|
||||
// List of enum values (e.g., lib)
|
||||
elemMap := elem.EnumMap()
|
||||
if strs, ok := value.([]string); ok {
|
||||
serialized := make([]string, 0, len(strs))
|
||||
for _, s := range strs {
|
||||
// lib values are already stored as the d.ts filename, need to find original key
|
||||
found := getNameOfCompilerOptionValue(s, elemMap)
|
||||
if found != "" {
|
||||
serialized = append(serialized, found)
|
||||
} else {
|
||||
serialized = append(serialized, s)
|
||||
}
|
||||
}
|
||||
result.Set(name, serialized)
|
||||
continue
|
||||
}
|
||||
}
|
||||
result.Set(name, value)
|
||||
|
||||
case CommandLineOptionTypeString:
|
||||
if optionDecl.IsFilePath {
|
||||
// File path option - make relative to config
|
||||
if s, ok := value.(string); ok && s != "" {
|
||||
absPath := tspath.GetNormalizedAbsolutePath(s, configDir)
|
||||
result.Set(name, tspath.GetRelativePathFromFile(configFilePath, absPath, comparePathsOptions))
|
||||
continue
|
||||
}
|
||||
}
|
||||
result.Set(name, value)
|
||||
|
||||
case CommandLineOptionTypeBoolean:
|
||||
if t, ok := value.(core.Tristate); ok {
|
||||
if t.IsTrue() {
|
||||
result.Set(name, true)
|
||||
} else if t.IsFalse() {
|
||||
result.Set(name, false)
|
||||
}
|
||||
} else {
|
||||
result.Set(name, value)
|
||||
}
|
||||
|
||||
case CommandLineOptionTypeNumber:
|
||||
result.Set(name, value)
|
||||
|
||||
default:
|
||||
result.Set(name, value)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// serializeEnumValue converts an enum field value to its corresponding string key
|
||||
// using the option's enum map. It handles int32-based enum types.
|
||||
func serializeEnumValue(value any, enumMap *collections.OrderedMap[string, any]) string {
|
||||
// The enum maps store values as core.ModuleKind, core.ScriptTarget, etc.
|
||||
// But those are all int32 underneath. We need to compare by the underlying int32 value.
|
||||
rv := reflect.ValueOf(value)
|
||||
if rv.CanInt() {
|
||||
intVal := rv.Int()
|
||||
for k, v := range enumMap.Entries() {
|
||||
ev := reflect.ValueOf(v)
|
||||
if ev.CanInt() && ev.Int() == intVal {
|
||||
return k
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fallback: direct comparison
|
||||
return getNameOfCompilerOptionValue(value, enumMap)
|
||||
}
|
||||
|
||||
// addImpliedOptions adds compiler options that are implied by other explicitly-set options,
|
||||
// mirroring TypeScript's convertToTSConfig behavior for computedOptions.
|
||||
// For example, when module: nodenext is set, moduleResolution: nodenext is implied.
|
||||
func addImpliedOptions(
|
||||
optionMap *collections.OrderedMap[string, any],
|
||||
options *core.CompilerOptions,
|
||||
_ string,
|
||||
_ tspath.ComparePathsOptions,
|
||||
) {
|
||||
// Build the set of explicitly provided option JSON names (e.g., "module", "target").
|
||||
provided := make(map[string]bool, optionMap.Size())
|
||||
for k := range optionMap.Keys() {
|
||||
provided[k] = true
|
||||
}
|
||||
|
||||
defaultOpts := &core.CompilerOptions{}
|
||||
|
||||
for _, entry := range impliedOptions {
|
||||
// Get the option declaration for this implied option (using case-insensitive lookup).
|
||||
optionDecl := CommandLineCompilerOptionsMap.Get(entry.name)
|
||||
if optionDecl == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip if this option is already explicitly provided.
|
||||
if provided[optionDecl.Name] {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if any direct dependency is in the provided set.
|
||||
// This mirrors TypeScript's optionDependsOn check.
|
||||
if !anyDependencyProvided(entry.dependencies, provided) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Compute the effective value with current options and the default value with empty options.
|
||||
implied := entry.compute(options)
|
||||
defaultVal := entry.compute(defaultOpts)
|
||||
|
||||
// If the implied value equals the default, this option doesn't add useful information.
|
||||
if reflect.DeepEqual(implied, defaultVal) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Serialize the implied value and add it to the option map.
|
||||
serialized := serializeImpliedOptionValue(optionDecl, implied)
|
||||
if serialized == nil {
|
||||
continue
|
||||
}
|
||||
optionMap.Set(optionDecl.Name, serialized)
|
||||
}
|
||||
}
|
||||
|
||||
// anyDependencyProvided returns true if any of the given dependency names
|
||||
// (using Go field names like "Target") corresponds to an option in the provided set.
|
||||
func anyDependencyProvided(dependencies []string, provided map[string]bool) bool {
|
||||
for _, dep := range dependencies {
|
||||
depDecl := CommandLineCompilerOptionsMap.Get(dep)
|
||||
if depDecl != nil && provided[depDecl.Name] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// serializeImpliedOptionValue converts a computed implied option value to its serializable form.
|
||||
// For enum options, it converts numeric values to their string names.
|
||||
// For boolean options, it returns the bool directly.
|
||||
func serializeImpliedOptionValue(optionDecl *CommandLineOption, value any) any {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
enumMap := optionDecl.EnumMap()
|
||||
if enumMap != nil {
|
||||
s := serializeEnumValue(value, enumMap)
|
||||
if s != "" {
|
||||
return s
|
||||
}
|
||||
return nil
|
||||
}
|
||||
switch v := value.(type) {
|
||||
case bool:
|
||||
return v
|
||||
case core.Tristate:
|
||||
if v.IsTrue() {
|
||||
return true
|
||||
} else if v.IsFalse() {
|
||||
return false
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return value
|
||||
}
|
||||
Reference in New Issue
Block a user