Skip to content

Commit 3cfa46c

Browse files
Implement GenerateTSConfig function and update tsc --init handler
Co-authored-by: RyanCavanaugh <[email protected]>
1 parent c0b12ac commit 3cfa46c

File tree

2 files changed

+193
-1
lines changed

2 files changed

+193
-1
lines changed

internal/execute/tsc.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,8 @@ func tscCompilation(sys tsc.System, commandLine *tsoptions.ParsedCommandLine, te
113113
}
114114

115115
if commandLine.CompilerOptions().Init.IsTrue() {
116-
return tsc.CommandLineResult{Status: tsc.ExitStatusNotImplemented}
116+
writeConfigFile(sys, reportDiagnostic, commandLine.CompilerOptions())
117+
return tsc.CommandLineResult{Status: tsc.ExitStatusSuccess}
117118
}
118119

119120
if commandLine.CompilerOptions().Version.IsTrue() {
@@ -317,3 +318,22 @@ func showConfig(sys tsc.System, config *core.CompilerOptions) {
317318
// !!!
318319
_ = jsonutil.MarshalIndentWrite(sys.Writer(), config, "", " ")
319320
}
321+
322+
func writeConfigFile(sys tsc.System, reportDiagnostic tsc.DiagnosticReporter, options *core.CompilerOptions) {
323+
currentDirectory := sys.GetCurrentDirectory()
324+
file := tspath.NormalizePath(tspath.CombinePaths(currentDirectory, "tsconfig.json"))
325+
326+
if sys.FS().FileExists(file) {
327+
reportDiagnostic(ast.NewCompilerDiagnostic(diagnostics.A_tsconfig_json_file_is_already_defined_at_Colon_0, file))
328+
} else {
329+
content := tsoptions.GenerateTSConfig(options, "\n")
330+
if err := sys.FS().WriteFile(file, content, false); err != nil {
331+
fmt.Fprintf(sys.Writer(), "Error writing tsconfig.json: %v\n", err)
332+
return
333+
}
334+
335+
fmt.Fprintf(sys.Writer(), "\n")
336+
fmt.Fprintf(sys.Writer(), "Created a new tsconfig.json\n")
337+
fmt.Fprintf(sys.Writer(), "You can learn more at https://aka.ms/tsconfig\n")
338+
}
339+
}

internal/tsoptions/commandlineparser.go

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package tsoptions
22

33
import (
4+
"fmt"
5+
"reflect"
46
"strconv"
57
"strings"
68

@@ -393,3 +395,173 @@ func convertJsonOptionOfEnumType(
393395
}
394396
return nil, []*ast.Diagnostic{createDiagnosticForInvalidEnumType(opt, sourceFile, valueExpression)}
395397
}
398+
399+
// GenerateTSConfig generates a tsconfig.json configuration when running command line "--init"
400+
func GenerateTSConfig(options *core.CompilerOptions, newLine string) string {
401+
tab := " "
402+
result := []string{}
403+
404+
result = append(result, "{")
405+
result = append(result, tab+"// "+diagnostics.Visit_https_Colon_Slash_Slashaka_ms_Slashtsconfig_to_read_more_about_this_file.Format())
406+
result = append(result, tab+"\"compilerOptions\": {")
407+
408+
emitHeader := func(header *diagnostics.Message) {
409+
result = append(result, tab+tab+"// "+header.Format())
410+
}
411+
412+
newline := func() {
413+
result = append(result, "")
414+
}
415+
416+
emitOption := func(setting string, defaultValue any, commented ...string) {
417+
commentMode := "never"
418+
if len(commented) > 0 {
419+
commentMode = commented[0]
420+
}
421+
422+
var comment bool
423+
if commentMode == "always" {
424+
comment = true
425+
} else if commentMode == "never" {
426+
comment = false
427+
} else {
428+
// "optional" - always comment out for the template
429+
comment = true
430+
}
431+
432+
formattedValue := formatValueOrArray(setting, defaultValue)
433+
434+
if comment {
435+
result = append(result, tab+tab+"// \""+setting+"\": "+formattedValue+",")
436+
} else {
437+
result = append(result, tab+tab+"\""+setting+"\": "+formattedValue+",")
438+
}
439+
}
440+
441+
// File Layout
442+
emitHeader(diagnostics.File_Layout)
443+
emitOption("rootDir", "./src", "optional")
444+
emitOption("outDir", "./dist", "optional")
445+
446+
newline()
447+
448+
// Environment Settings
449+
emitHeader(diagnostics.Environment_Settings)
450+
emitHeader(diagnostics.See_also_https_Colon_Slash_Slashaka_ms_Slashtsconfig_Slashmodule)
451+
emitOption("module", core.ModuleKindNodeNext)
452+
emitOption("target", core.ScriptTargetESNext)
453+
emitOption("types", []any{})
454+
if options.Lib != nil && len(options.Lib) > 0 {
455+
emitOption("lib", options.Lib)
456+
}
457+
emitHeader(diagnostics.For_nodejs_Colon)
458+
result = append(result, tab+tab+"// \"lib\": [\"esnext\"],")
459+
result = append(result, tab+tab+"// \"types\": [\"node\"],")
460+
emitHeader(diagnostics.X_and_npm_install_D_types_Slashnode)
461+
462+
newline()
463+
464+
// Other Outputs
465+
emitHeader(diagnostics.Other_Outputs)
466+
emitOption("sourceMap", true)
467+
emitOption("declaration", true)
468+
emitOption("declarationMap", true)
469+
470+
newline()
471+
472+
// Stricter Typechecking Options
473+
emitHeader(diagnostics.Stricter_Typechecking_Options)
474+
emitOption("noUncheckedIndexedAccess", true)
475+
emitOption("exactOptionalPropertyTypes", true)
476+
477+
newline()
478+
479+
// Style Options
480+
emitHeader(diagnostics.Style_Options)
481+
emitOption("noImplicitReturns", true, "optional")
482+
emitOption("noImplicitOverride", true, "optional")
483+
emitOption("noUnusedLocals", true, "optional")
484+
emitOption("noUnusedParameters", true, "optional")
485+
emitOption("noFallthroughCasesInSwitch", true, "optional")
486+
emitOption("noPropertyAccessFromIndexSignature", true, "optional")
487+
488+
newline()
489+
490+
// Recommended Options
491+
emitHeader(diagnostics.Recommended_Options)
492+
emitOption("strict", true)
493+
emitOption("jsx", core.JsxEmitReactJSX)
494+
emitOption("verbatimModuleSyntax", true)
495+
emitOption("isolatedModules", true)
496+
emitOption("noUncheckedSideEffectImports", true)
497+
emitOption("moduleDetection", core.ModuleDetectionKindForce)
498+
// Last option - no trailing comma
499+
result = append(result, tab+tab+"\"skipLibCheck\": true")
500+
501+
result = append(result, tab+"}")
502+
result = append(result, "}")
503+
result = append(result, "")
504+
505+
return strings.Join(result, newLine)
506+
}
507+
508+
func formatValueOrArray(settingName string, value any) string {
509+
option := CommandLineCompilerOptionsMap.Get(settingName)
510+
if option == nil {
511+
// Fallback for unknown options
512+
return formatSingleValue(value, nil)
513+
}
514+
515+
typeMap := option.EnumMap()
516+
517+
// Handle array values
518+
if v, ok := value.([]any); ok {
519+
var elementMap *collections.OrderedMap[string, any]
520+
if element := option.Elements(); element != nil {
521+
elementMap = element.EnumMap()
522+
}
523+
formattedValues := make([]string, len(v))
524+
for i, item := range v {
525+
formattedValues[i] = formatSingleValue(item, elementMap)
526+
}
527+
return "[" + strings.Join(formattedValues, ", ") + "]"
528+
}
529+
530+
// Handle string array (lib option)
531+
if v, ok := value.([]string); ok {
532+
formattedValues := make([]string, len(v))
533+
for i, item := range v {
534+
formattedValues[i] = formatSingleValue(item, nil)
535+
}
536+
return "[" + strings.Join(formattedValues, ", ") + "]"
537+
}
538+
539+
return formatSingleValue(value, typeMap)
540+
}
541+
542+
func formatSingleValue(value any, typeMap *collections.OrderedMap[string, any]) string {
543+
if typeMap != nil {
544+
// Find the key for this enum value
545+
for k, v := range typeMap.Entries() {
546+
if v == value {
547+
value = k
548+
break
549+
}
550+
}
551+
}
552+
553+
// Handle different types
554+
switch v := value.(type) {
555+
case string:
556+
return strconv.Quote(v)
557+
case bool:
558+
return strconv.FormatBool(v)
559+
case int, int32, int64:
560+
return strconv.FormatInt(reflect.ValueOf(v).Int(), 10)
561+
case float32, float64:
562+
return strconv.FormatFloat(reflect.ValueOf(v).Float(), 'f', -1, 64)
563+
default:
564+
// For unknown types, use string representation
565+
return strconv.Quote(fmt.Sprintf("%v", v))
566+
}
567+
}

0 commit comments

Comments
 (0)