Skip to content
Open
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
4 changes: 3 additions & 1 deletion internal/execute/tsc.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"strings"

"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/collections"
"github.com/microsoft/typescript-go/internal/compiler"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/diagnostics"
Expand Down Expand Up @@ -113,7 +114,8 @@ func tscCompilation(sys tsc.System, commandLine *tsoptions.ParsedCommandLine, te
}

if commandLine.CompilerOptions().Init.IsTrue() {
return tsc.CommandLineResult{Status: tsc.ExitStatusNotImplemented}
tsc.WriteConfigFile(sys, reportDiagnostic, commandLine.Raw.(*collections.OrderedMap[string, any]))
return tsc.CommandLineResult{Status: tsc.ExitStatusSuccess}
}

if commandLine.CompilerOptions().Version.IsTrue() {
Expand Down
216 changes: 216 additions & 0 deletions internal/execute/tsc/init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
package tsc

import (
"fmt"
"reflect"
"slices"
"strings"

"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/collections"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/diagnostics"
"github.com/microsoft/typescript-go/internal/jsonutil"
"github.com/microsoft/typescript-go/internal/tsoptions"
"github.com/microsoft/typescript-go/internal/tspath"
)

func WriteConfigFile(sys System, reportDiagnostic DiagnosticReporter, options *collections.OrderedMap[string, any]) {
getCurrentDirectory := sys.GetCurrentDirectory()
file := tspath.NormalizePath(tspath.CombinePaths(getCurrentDirectory, "tsconfig.json"))
if sys.FS().FileExists(file) {
reportDiagnostic(ast.NewCompilerDiagnostic(diagnostics.A_tsconfig_json_file_is_already_defined_at_Colon_0, file))
} else {
_ = sys.FS().WriteFile(file, generateTSConfig(options), false)
output := []string{"\n"}
output = append(output, getHeader(sys, "Created a new tsconfig.json")...)
output = append(output, "You can learn more at https://aka.ms/tsconfig", "\n")
fmt.Fprint(sys.Writer(), strings.Join(output, ""))
}
}

func generateTSConfig(options *collections.OrderedMap[string, any]) string {
const tab = " "
var result []string

allSetOptions := make([]string, 0, options.Size())
for k := range options.Keys() {
if k != "init" && k != "help" && k != "watch" {
allSetOptions = append(allSetOptions, k)
}
}

// !!! locale getLocaleSpecificMessage
emitHeader := func(header *diagnostics.Message) {
result = append(result, tab+tab+"// "+header.Format())
}
newline := func() {
result = append(result, "")
}
push := func(args ...string) {
result = append(result, args...)
}

formatSingleValue := func(value any, enumMap *collections.OrderedMap[string, any]) string {
if enumMap != nil {
var found bool
for k, v := range enumMap.Entries() {
if value == v {
value = k
found = true
break
}
}
if !found {
panic(fmt.Sprintf("No matching value of %v", value))
}
}

b, err := jsonutil.MarshalIndent(value, "", "")
if err != nil {
panic(fmt.Sprintf("should not happen: %v", err))
}
return string(b)
}

formatValueOrArray := func(settingName string, value any) string {
var option *tsoptions.CommandLineOption
for _, decl := range tsoptions.OptionsDeclarations {
if decl.Name == settingName {
option = decl
}
}
if option == nil {
panic(`No option named ` + settingName)
}

rval := reflect.ValueOf(value)
if rval.Kind() == reflect.Slice {
var enumMap *collections.OrderedMap[string, any]
if elemOption := option.Elements(); elemOption != nil {
enumMap = elemOption.EnumMap()
}

var elems []string
for i := range rval.Len() {
elems = append(elems, formatSingleValue(rval.Index(i).Interface(), enumMap))
}
return `[` + strings.Join(elems, ", ") + `]`
} else {
return formatSingleValue(value, option.EnumMap())
}
}

// commentedNever': Never comment this out
// commentedAlways': Always comment this out, even if it's on commandline
// commentedOptional': Comment out unless it's on commandline
type commented int
const (
commentedNever commented = iota
commentedAlways
commentedOptional
)
emitOption := func(setting string, defaultValue any, commented commented) {
if commented > 2 {
panic("should not happen: invalid `commented`, must be a bug.")
}

existingOptionIndex := slices.Index(allSetOptions, setting)
if existingOptionIndex >= 0 {
allSetOptions = slices.Delete(allSetOptions, existingOptionIndex, existingOptionIndex+1)
}

var comment bool
switch commented {
case commentedAlways:
comment = true
case commentedNever:
comment = false
default:
comment = !options.Has(setting)
}

value, ok := options.Get(setting)
if !ok {
value = defaultValue
}

if comment {
push(tab + tab + `// "` + setting + `": ` + formatValueOrArray(setting, value) + `,`)
} else {
push(tab + tab + `"` + setting + `": ` + formatValueOrArray(setting, value) + `,`)
}
}

push("{")
// !!! locale getLocaleSpecificMessage
push(tab + `// ` + diagnostics.Visit_https_Colon_Slash_Slashaka_ms_Slashtsconfig_to_read_more_about_this_file.Format())
push(tab + `"compilerOptions": {`)

emitHeader(diagnostics.File_Layout)
emitOption("rootDir", "./src", commentedOptional)
emitOption("outDir", "./dist", commentedOptional)

newline()

emitHeader(diagnostics.Environment_Settings)
emitHeader(diagnostics.See_also_https_Colon_Slash_Slashaka_ms_Slashtsconfig_Slashmodule)
emitOption("module", core.ModuleKindNodeNext, commentedNever)
emitOption("target", core.ScriptTargetESNext, commentedNever)
emitOption("types", []any{}, commentedNever)
if lib, ok := options.Get("lib"); ok {
emitOption("lib", lib, commentedNever)
}
emitHeader(diagnostics.For_nodejs_Colon)
push(tab + tab + `// "lib": ["esnext"],`)
push(tab + tab + `// "types": ["node"],`)
emitHeader(diagnostics.X_and_npm_install_D_types_Slashnode)

newline()

emitHeader(diagnostics.Other_Outputs)
emitOption("sourceMap" /*defaultValue*/, true, commentedNever)
emitOption("declaration" /*defaultValue*/, true, commentedNever)
emitOption("declarationMap" /*defaultValue*/, true, commentedNever)

newline()

emitHeader(diagnostics.Stricter_Typechecking_Options)
emitOption("noUncheckedIndexedAccess" /*defaultValue*/, true, commentedNever)
emitOption("exactOptionalPropertyTypes" /*defaultValue*/, true, commentedNever)

newline()

emitHeader(diagnostics.Style_Options)
emitOption("noImplicitReturns" /*defaultValue*/, true, commentedOptional)
emitOption("noImplicitOverride" /*defaultValue*/, true, commentedOptional)
emitOption("noUnusedLocals" /*defaultValue*/, true, commentedOptional)
emitOption("noUnusedParameters" /*defaultValue*/, true, commentedOptional)
emitOption("noFallthroughCasesInSwitch" /*defaultValue*/, true, commentedOptional)
emitOption("noPropertyAccessFromIndexSignature" /*defaultValue*/, true, commentedOptional)

newline()

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

// Write any user-provided options we haven't already
if len(allSetOptions) > 0 {
newline()
for len(allSetOptions) > 0 {
emitOption(allSetOptions[0], options.GetOrZero(allSetOptions[0]), commentedNever)
}
}

push(tab + "}")
push(`}`)
push(``)

return strings.Join(result, "\n")
}
54 changes: 54 additions & 0 deletions internal/execute/tsctests/tsc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,60 @@ func TestTscCommandline(t *testing.T) {
subScenario: "when build not first argument",
commandLineArgs: []string{"--verbose", "--build"},
},
{
subScenario: "Initialized TSConfig with files options",
commandLineArgs: []string{"--init", "file0.st", "file1.ts", "file2.ts"},
},
{
subScenario: "Initialized TSConfig with boolean value compiler options",
commandLineArgs: []string{"--init", "--noUnusedLocals"},
},
{
subScenario: "Initialized TSConfig with enum value compiler options",
commandLineArgs: []string{"--init", "--target", "es5", "--jsx", "react"},
},
{
subScenario: "Initialized TSConfig with list compiler options",
commandLineArgs: []string{"--init", "--types", "jquery,mocha"},
},
{
subScenario: "Initialized TSConfig with list compiler options with enum value",
commandLineArgs: []string{"--init", "--lib", "es5,es2015.core"},
},
{
subScenario: "Initialized TSConfig with incorrect compiler option",
commandLineArgs: []string{"--init", "--someNonExistOption"},
},
{
subScenario: "Initialized TSConfig with incorrect compiler option value",
commandLineArgs: []string{"--init", "--lib", "nonExistLib,es5,es2015.promise"},
},
{
subScenario: "Initialized TSConfig with advanced options",
commandLineArgs: []string{"--init", "--declaration", "--declarationDir", "lib", "--skipLibCheck", "--noErrorTruncation"},
},
{
subScenario: "Initialized TSConfig with --help",
commandLineArgs: []string{"--init", "--help"},
},
{
subScenario: "Initialized TSConfig with --watch",
commandLineArgs: []string{"--init", "--watch"},
},
{
subScenario: "Initialized TSConfig with tsconfig.json",
commandLineArgs: []string{"--init"},
files: FileMap{
"/home/src/workspaces/project/first.ts": `export const a = 1`,
"/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(`
{
"compilerOptions": {
"strict": true,
"noEmit": true
}
}`),
},
},
{
subScenario: "help",
commandLineArgs: []string{"--help"},
Expand Down
2 changes: 1 addition & 1 deletion internal/tsoptions/commandlineparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func ParseCommandLine(
commandLine = []string{}
}
parser := parseCommandLineWorker(CompilerOptionsDidYouMeanDiagnostics, commandLine, host.FS())
optionsWithAbsolutePaths := convertToOptionsWithAbsolutePaths(parser.options, CommandLineCompilerOptionsMap, host.GetCurrentDirectory())
optionsWithAbsolutePaths := convertToOptionsWithAbsolutePaths(parser.options.Clone(), CommandLineCompilerOptionsMap, host.GetCurrentDirectory())
compilerOptions := convertMapToOptions(optionsWithAbsolutePaths, &compilerOptionsParser{&core.CompilerOptions{}}).CompilerOptions
watchOptions := convertMapToOptions(optionsWithAbsolutePaths, &watchOptionsParser{&core.WatchOptions{}}).WatchOptions
result := NewParsedCommandLine(compilerOptions, parser.fileNames, tspath.ComparePathsOptions{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
currentDirectory::/home/src/workspaces/project
useCaseSensitiveFileNames::true
Input::

tsgo --init --help
ExitStatus:: Success
Output::

Created a new tsconfig.json

You can learn more at https://aka.ms/tsconfig
//// [/home/src/workspaces/project/tsconfig.json] *new*
{
// Visit https://aka.ms/tsconfig to read more about this file
"compilerOptions": {
// File Layout
// "rootDir": "./src",
// "outDir": "./dist",

// Environment Settings
// See also https://aka.ms/tsconfig/module
"module": "nodenext",
"target": "esnext",
"types": [],
// For nodejs:
// "lib": ["esnext"],
// "types": ["node"],
// and npm install -D @types/node

// Other Outputs
"sourceMap": true,
"declaration": true,
"declarationMap": true,

// Stricter Typechecking Options
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,

// Style Options
// "noImplicitReturns": true,
// "noImplicitOverride": true,
// "noUnusedLocals": true,
// "noUnusedParameters": true,
// "noFallthroughCasesInSwitch": true,
// "noPropertyAccessFromIndexSignature": true,

// Recommended Options
"strict": true,
"jsx": "react-jsx",
"verbatimModuleSyntax": true,
"isolatedModules": true,
"noUncheckedSideEffectImports": true,
"moduleDetection": "force",
"skipLibCheck": true,
}
}


Loading