Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion internal/execute/tsc.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@ func tscCompilation(sys tsc.System, commandLine *tsoptions.ParsedCommandLine, te
}

if commandLine.CompilerOptions().Init.IsTrue() {
return tsc.CommandLineResult{Status: tsc.ExitStatusNotImplemented}
writeConfigFile(sys, reportDiagnostic, commandLine.CompilerOptions())
return tsc.CommandLineResult{Status: tsc.ExitStatusSuccess}
}

if commandLine.CompilerOptions().Version.IsTrue() {
Expand Down Expand Up @@ -317,3 +318,22 @@ func showConfig(sys tsc.System, config *core.CompilerOptions) {
// !!!
_ = jsonutil.MarshalIndentWrite(sys.Writer(), config, "", " ")
}

func writeConfigFile(sys tsc.System, reportDiagnostic tsc.DiagnosticReporter, options *core.CompilerOptions) {
currentDirectory := sys.GetCurrentDirectory()
file := tspath.NormalizePath(tspath.CombinePaths(currentDirectory, "tsconfig.json"))

if sys.FS().FileExists(file) {
reportDiagnostic(ast.NewCompilerDiagnostic(diagnostics.A_tsconfig_json_file_is_already_defined_at_Colon_0, file))
} else {
content := tsoptions.GenerateTSConfig(options, "\n")
if err := sys.FS().WriteFile(file, content, false); err != nil {
fmt.Fprintf(sys.Writer(), "Error writing tsconfig.json: %v\n", err)
return
}

fmt.Fprintf(sys.Writer(), "\n")
fmt.Fprintf(sys.Writer(), "Created a new tsconfig.json\n")
fmt.Fprintf(sys.Writer(), "You can learn more at https://aka.ms/tsconfig\n")
}
}
171 changes: 171 additions & 0 deletions internal/tsoptions/commandlineparser.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package tsoptions

import (
"fmt"
"reflect"
"strconv"
"strings"

Expand Down Expand Up @@ -393,3 +395,172 @@ func convertJsonOptionOfEnumType(
}
return nil, []*ast.Diagnostic{createDiagnosticForInvalidEnumType(opt, sourceFile, valueExpression)}
}

// GenerateTSConfig generates a tsconfig.json configuration when running command line "--init"
func GenerateTSConfig(options *core.CompilerOptions, newLine string) string {
tab := " "
result := []string{}

result = append(result, "{")
result = append(result, tab+"// "+diagnostics.Visit_https_Colon_Slash_Slashaka_ms_Slashtsconfig_to_read_more_about_this_file.Format())
result = append(result, tab+"\"compilerOptions\": {")

emitHeader := func(header *diagnostics.Message) {
result = append(result, tab+tab+"// "+header.Format())
}

newline := func() {
result = append(result, "")
}

emitOption := func(setting string, defaultValue any, commented ...string) {
commentMode := "never"
if len(commented) > 0 {
commentMode = commented[0]
}

var comment bool
if commentMode == "always" {
comment = true
} else if commentMode == "never" {
comment = false
} else {
// "optional" - always comment out for the template
comment = true
}

formattedValue := formatValueOrArray(setting, defaultValue)

if comment {
result = append(result, tab+tab+"// \""+setting+"\": "+formattedValue+",")
} else {
result = append(result, tab+tab+"\""+setting+"\": "+formattedValue+",")
}
}

// File Layout
emitHeader(diagnostics.File_Layout)
emitOption("rootDir", "./src", "optional")
emitOption("outDir", "./dist", "optional")

newline()

// Environment Settings
emitHeader(diagnostics.Environment_Settings)
emitHeader(diagnostics.See_also_https_Colon_Slash_Slashaka_ms_Slashtsconfig_Slashmodule)
emitOption("module", core.ModuleKindNodeNext)
emitOption("target", core.ScriptTargetESNext)
emitOption("types", []any{})
if options.Lib != nil && len(options.Lib) > 0 {
emitOption("lib", options.Lib)
}
emitHeader(diagnostics.For_nodejs_Colon)
result = append(result, tab+tab+"// \"lib\": [\"esnext\"],")
result = append(result, tab+tab+"// \"types\": [\"node\"],")
emitHeader(diagnostics.X_and_npm_install_D_types_Slashnode)

newline()

// Other Outputs
emitHeader(diagnostics.Other_Outputs)
emitOption("sourceMap", true)
emitOption("declaration", true)
emitOption("declarationMap", true)

newline()

// Stricter Typechecking Options
emitHeader(diagnostics.Stricter_Typechecking_Options)
emitOption("noUncheckedIndexedAccess", true)
emitOption("exactOptionalPropertyTypes", true)

newline()

// Style Options
emitHeader(diagnostics.Style_Options)
emitOption("noImplicitReturns", true, "optional")
emitOption("noImplicitOverride", true, "optional")
emitOption("noUnusedLocals", true, "optional")
emitOption("noUnusedParameters", true, "optional")
emitOption("noFallthroughCasesInSwitch", true, "optional")
emitOption("noPropertyAccessFromIndexSignature", true, "optional")

newline()

// Recommended Options
emitHeader(diagnostics.Recommended_Options)
emitOption("strict", true)
emitOption("jsx", core.JsxEmitReactJSX)
emitOption("verbatimModuleSyntax", true)
emitOption("isolatedModules", true)
emitOption("noUncheckedSideEffectImports", true)
emitOption("moduleDetection", core.ModuleDetectionKindForce)
emitOption("skipLibCheck", true)

result = append(result, tab+"}")
result = append(result, "}")
result = append(result, "")

return strings.Join(result, newLine)
}

func formatValueOrArray(settingName string, value any) string {
option := CommandLineCompilerOptionsMap.Get(settingName)
if option == nil {
// Fallback for unknown options
return formatSingleValue(value, nil)
}

typeMap := option.EnumMap()

// Handle array values
if v, ok := value.([]any); ok {
var elementMap *collections.OrderedMap[string, any]
if element := option.Elements(); element != nil {
elementMap = element.EnumMap()
}
formattedValues := make([]string, len(v))
for i, item := range v {
formattedValues[i] = formatSingleValue(item, elementMap)
}
return "[" + strings.Join(formattedValues, ", ") + "]"
}

// Handle string array (lib option)
if v, ok := value.([]string); ok {
formattedValues := make([]string, len(v))
for i, item := range v {
formattedValues[i] = formatSingleValue(item, nil)
}
return "[" + strings.Join(formattedValues, ", ") + "]"
}

return formatSingleValue(value, typeMap)
}

func formatSingleValue(value any, typeMap *collections.OrderedMap[string, any]) string {
if typeMap != nil {
// Find the key for this enum value
for k, v := range typeMap.Entries() {
if v == value {
value = k
break
}
}
}

// Handle different types
switch v := value.(type) {
case string:
return strconv.Quote(v)
case bool:
return strconv.FormatBool(v)
case int, int32, int64:
return strconv.FormatInt(reflect.ValueOf(v).Int(), 10)
case float32, float64:
return strconv.FormatFloat(reflect.ValueOf(v).Float(), 'f', -1, 64)
default:
// For unknown types, use string representation
return strconv.Quote(fmt.Sprintf("%v", v))
}
}