395 lines
14 KiB
Go
395 lines
14 KiB
Go
package tsc
|
|
|
|
import (
|
|
"fmt"
|
|
"slices"
|
|
"strings"
|
|
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/diagnostics"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tsoptions"
|
|
)
|
|
|
|
func PrintVersion(sys System) {
|
|
fmt.Fprintln(sys.Writer(), diagnostics.Version_0.Format(core.Version()))
|
|
}
|
|
|
|
func PrintHelp(sys System, commandLine *tsoptions.ParsedCommandLine) {
|
|
if commandLine.CompilerOptions().All.IsFalseOrUnknown() {
|
|
printEasyHelp(sys, getOptionsForHelp(commandLine))
|
|
} else {
|
|
// !!! printAllHelp(sys, getOptionsForHelp(commandLine))
|
|
}
|
|
}
|
|
|
|
func getOptionsForHelp(commandLine *tsoptions.ParsedCommandLine) []*tsoptions.CommandLineOption {
|
|
// Sort our options by their names, (e.g. "--noImplicitAny" comes before "--watch")
|
|
opts := slices.Clone(tsoptions.OptionsDeclarations)
|
|
opts = append(opts, &tsoptions.TscBuildOption)
|
|
|
|
if commandLine.CompilerOptions().All.IsTrue() {
|
|
slices.SortFunc(opts, func(a, b *tsoptions.CommandLineOption) int {
|
|
return strings.Compare(strings.ToLower(a.Name), strings.ToLower(b.Name))
|
|
})
|
|
return opts
|
|
} else {
|
|
return core.Filter(opts, func(opt *tsoptions.CommandLineOption) bool {
|
|
return opt.ShowInSimplifiedHelpView
|
|
})
|
|
}
|
|
}
|
|
|
|
func getHeader(sys System, message string) []string {
|
|
colors := createColors(sys)
|
|
header := make([]string, 0, 3)
|
|
terminalWidth := sys.GetWidthOfTerminal()
|
|
const tsIcon = " "
|
|
const tsIconTS = " TS "
|
|
const tsIconLength = len(tsIcon)
|
|
|
|
tsIconFirstLine := colors.blueBackground(tsIcon)
|
|
tsIconSecondLine := colors.blueBackground(colors.brightWhite(tsIconTS))
|
|
// If we have enough space, print TS icon.
|
|
if terminalWidth >= len(message)+tsIconLength {
|
|
// right align of the icon is 120 at most.
|
|
rightAlign := core.IfElse(terminalWidth > 120, 120, terminalWidth)
|
|
leftAlign := rightAlign - tsIconLength
|
|
header = append(header, fmt.Sprintf("%-*s", leftAlign, message), tsIconFirstLine, "\n")
|
|
header = append(header, strings.Repeat(" ", leftAlign), tsIconSecondLine, "\n")
|
|
} else {
|
|
header = append(header, message, "\n", "\n")
|
|
}
|
|
return header
|
|
}
|
|
|
|
func printEasyHelp(sys System, simpleOptions []*tsoptions.CommandLineOption) {
|
|
colors := createColors(sys)
|
|
var output []string
|
|
example := func(examples []string, desc *diagnostics.Message) {
|
|
for _, example := range examples {
|
|
output = append(output, " ", colors.blue(example), "\n")
|
|
}
|
|
output = append(output, " ", desc.Format(), "\n", "\n")
|
|
}
|
|
|
|
msg := diagnostics.X_tsc_Colon_The_TypeScript_Compiler.Format() + " - " + diagnostics.Version_0.Format(core.Version())
|
|
output = append(output, getHeader(sys, msg)...)
|
|
|
|
output = append(output, colors.bold(diagnostics.COMMON_COMMANDS.Format()), "\n", "\n")
|
|
|
|
example([]string{"tsc"}, diagnostics.Compiles_the_current_project_tsconfig_json_in_the_working_directory)
|
|
example([]string{"tsc app.ts util.ts"}, diagnostics.Ignoring_tsconfig_json_compiles_the_specified_files_with_default_compiler_options)
|
|
example([]string{"tsc -b"}, diagnostics.Build_a_composite_project_in_the_working_directory)
|
|
example([]string{"tsc --init"}, diagnostics.Creates_a_tsconfig_json_with_the_recommended_settings_in_the_working_directory)
|
|
example([]string{"tsc -p ./path/to/tsconfig.json"}, diagnostics.Compiles_the_TypeScript_project_located_at_the_specified_path)
|
|
example([]string{"tsc --help --all"}, diagnostics.An_expanded_version_of_this_information_showing_all_possible_compiler_options)
|
|
example([]string{"tsc --noEmit", "tsc --target esnext"}, diagnostics.Compiles_the_current_project_with_additional_settings)
|
|
|
|
var cliCommands []*tsoptions.CommandLineOption
|
|
var configOpts []*tsoptions.CommandLineOption
|
|
for _, opt := range simpleOptions {
|
|
if opt.IsCommandLineOnly || opt.Category == diagnostics.Command_line_Options {
|
|
cliCommands = append(cliCommands, opt)
|
|
} else {
|
|
configOpts = append(configOpts, opt)
|
|
}
|
|
}
|
|
|
|
output = append(output, generateSectionOptionsOutput(sys, diagnostics.COMMAND_LINE_FLAGS.Format(), cliCommands /*subCategory*/, false /*beforeOptionsDescription*/, nil /*afterOptionsDescription*/, nil)...)
|
|
|
|
// !!! locale formatMessage
|
|
after := diagnostics.You_can_learn_about_all_of_the_compiler_options_at_0.Format("https://aka.ms/tsc")
|
|
output = append(output, generateSectionOptionsOutput(sys, diagnostics.COMMON_COMPILER_OPTIONS.Format(), configOpts /*subCategory*/, false /*beforeOptionsDescription*/, nil, &after)...)
|
|
|
|
for _, chunk := range output {
|
|
fmt.Fprint(sys.Writer(), chunk)
|
|
}
|
|
}
|
|
|
|
func PrintBuildHelp(sys System, buildOptions []*tsoptions.CommandLineOption) {
|
|
var output []string
|
|
output = append(output, getHeader(sys, diagnostics.X_tsc_Colon_The_TypeScript_Compiler.Format()+" - "+diagnostics.Version_0.Format(core.Version()))...)
|
|
before := diagnostics.Using_build_b_will_make_tsc_behave_more_like_a_build_orchestrator_than_a_compiler_This_is_used_to_trigger_building_composite_projects_which_you_can_learn_more_about_at_0.Format("https://aka.ms/tsc-composite-builds")
|
|
options := core.Filter(buildOptions, func(option *tsoptions.CommandLineOption) bool {
|
|
return option != &tsoptions.TscBuildOption
|
|
})
|
|
output = append(output, generateSectionOptionsOutput(sys, diagnostics.BUILD_OPTIONS.Format(), options, false, &before, nil)...)
|
|
|
|
for _, chunk := range output {
|
|
fmt.Fprint(sys.Writer(), chunk)
|
|
}
|
|
}
|
|
|
|
func generateSectionOptionsOutput(
|
|
sys System,
|
|
sectionName string,
|
|
options []*tsoptions.CommandLineOption,
|
|
subCategory bool,
|
|
beforeOptionsDescription,
|
|
afterOptionsDescription *string,
|
|
) (output []string) {
|
|
output = append(output, createColors(sys).bold(sectionName), "\n", "\n")
|
|
|
|
if beforeOptionsDescription != nil {
|
|
output = append(output, *beforeOptionsDescription, "\n", "\n")
|
|
}
|
|
if !subCategory {
|
|
output = append(output, generateGroupOptionOutput(sys, options)...)
|
|
if afterOptionsDescription != nil {
|
|
output = append(output, *afterOptionsDescription, "\n", "\n")
|
|
}
|
|
return output
|
|
}
|
|
categoryMap := make(map[string][]*tsoptions.CommandLineOption)
|
|
for _, option := range options {
|
|
if option.Category == nil {
|
|
continue
|
|
}
|
|
curCategory := option.Category.Format()
|
|
categoryMap[curCategory] = append(categoryMap[curCategory], option)
|
|
}
|
|
for key, value := range categoryMap {
|
|
output = append(output, "### ", key, "\n", "\n")
|
|
output = append(output, generateGroupOptionOutput(sys, value)...)
|
|
}
|
|
if afterOptionsDescription != nil {
|
|
output = append(output, *afterOptionsDescription, "\n", "\n")
|
|
}
|
|
|
|
return output
|
|
}
|
|
|
|
func generateGroupOptionOutput(sys System, optionsList []*tsoptions.CommandLineOption) []string {
|
|
var maxLength int
|
|
for _, option := range optionsList {
|
|
curLenght := len(getDisplayNameTextOfOption(option))
|
|
maxLength = max(curLenght, maxLength)
|
|
}
|
|
|
|
// left part should be right align, right part should be left align
|
|
|
|
// assume 2 space between left margin and left part.
|
|
rightAlignOfLeftPart := maxLength + 2
|
|
// assume 2 space between left and right part
|
|
leftAlignOfRightPart := rightAlignOfLeftPart + 2
|
|
|
|
var lines []string
|
|
for _, option := range optionsList {
|
|
tmp := generateOptionOutput(sys, option, rightAlignOfLeftPart, leftAlignOfRightPart)
|
|
lines = append(lines, tmp...)
|
|
}
|
|
|
|
// make sure always a blank line in the end.
|
|
if len(lines) < 2 || lines[len(lines)-2] != "\n" {
|
|
lines = append(lines, "\n")
|
|
}
|
|
|
|
return lines
|
|
}
|
|
|
|
func generateOptionOutput(
|
|
sys System,
|
|
option *tsoptions.CommandLineOption,
|
|
rightAlignOfLeft, leftAlignOfRight int,
|
|
) []string {
|
|
var text []string
|
|
colors := createColors(sys)
|
|
|
|
// name and description
|
|
name := getDisplayNameTextOfOption(option)
|
|
|
|
// value type and possible value
|
|
valueCandidates := getValueCandidate(option)
|
|
|
|
var defaultValueDescription string
|
|
if msg, ok := option.DefaultValueDescription.(*diagnostics.Message); ok && msg != nil {
|
|
defaultValueDescription = msg.Format()
|
|
} else {
|
|
defaultValueDescription = formatDefaultValue(
|
|
option.DefaultValueDescription,
|
|
core.IfElse(
|
|
option.Kind == tsoptions.CommandLineOptionTypeList || option.Kind == tsoptions.CommandLineOptionTypeListOrElement,
|
|
option.Elements(), option,
|
|
),
|
|
)
|
|
}
|
|
|
|
terminalWidth := sys.GetWidthOfTerminal()
|
|
|
|
if terminalWidth >= 80 {
|
|
description := ""
|
|
if option.Description != nil {
|
|
description = option.Description.Format()
|
|
}
|
|
text = append(text, getPrettyOutput(colors, name, description, rightAlignOfLeft, leftAlignOfRight, terminalWidth, true /*colorLeft*/)...)
|
|
text = append(text, "\n")
|
|
if showAdditionalInfoOutput(valueCandidates, option) {
|
|
if valueCandidates != nil {
|
|
text = append(text, getPrettyOutput(colors, valueCandidates.valueType, valueCandidates.possibleValues, rightAlignOfLeft, leftAlignOfRight, terminalWidth, false /*colorLeft*/)...)
|
|
text = append(text, "\n")
|
|
}
|
|
if defaultValueDescription != "" {
|
|
text = append(text, getPrettyOutput(colors, diagnostics.X_default_Colon.Format(), defaultValueDescription, rightAlignOfLeft, leftAlignOfRight, terminalWidth, false /*colorLeft*/)...)
|
|
text = append(text, "\n")
|
|
}
|
|
}
|
|
text = append(text, "\n")
|
|
} else {
|
|
text = append(text, colors.blue(name), "\n")
|
|
if option.Description != nil {
|
|
text = append(text, option.Description.Format())
|
|
}
|
|
text = append(text, "\n")
|
|
if showAdditionalInfoOutput(valueCandidates, option) {
|
|
if valueCandidates != nil {
|
|
text = append(text, valueCandidates.valueType, " ", valueCandidates.possibleValues)
|
|
}
|
|
if defaultValueDescription != "" {
|
|
if valueCandidates != nil {
|
|
text = append(text, "\n")
|
|
}
|
|
text = append(text, diagnostics.X_default_Colon.Format(), " ", defaultValueDescription)
|
|
}
|
|
|
|
text = append(text, "\n")
|
|
}
|
|
text = append(text, "\n")
|
|
}
|
|
|
|
return text
|
|
}
|
|
|
|
func formatDefaultValue(defaultValue any, option *tsoptions.CommandLineOption) string {
|
|
if defaultValue == nil || defaultValue == core.TSUnknown {
|
|
return "undefined"
|
|
}
|
|
|
|
if option.Kind == tsoptions.CommandLineOptionTypeEnum {
|
|
// e.g. ScriptTarget.ES2015 -> "es6/es2015"
|
|
var names []string
|
|
for name, value := range option.EnumMap().Entries() {
|
|
if value == defaultValue {
|
|
names = append(names, name)
|
|
}
|
|
}
|
|
return strings.Join(names, "/")
|
|
}
|
|
return fmt.Sprintf("%v", defaultValue)
|
|
}
|
|
|
|
type valueCandidate struct {
|
|
// "one or more" or "any of"
|
|
valueType string
|
|
possibleValues string
|
|
}
|
|
|
|
func showAdditionalInfoOutput(valueCandidates *valueCandidate, option *tsoptions.CommandLineOption) bool {
|
|
if option.Category == diagnostics.Command_line_Options {
|
|
return false
|
|
}
|
|
if valueCandidates != nil && valueCandidates.possibleValues == "string" &&
|
|
(option.DefaultValueDescription == nil ||
|
|
option.DefaultValueDescription == "false" ||
|
|
option.DefaultValueDescription == "n/a") {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func getValueCandidate(option *tsoptions.CommandLineOption) *valueCandidate {
|
|
// option.type might be "string" | "number" | "boolean" | "object" | "list" | Map<string, number | string>
|
|
// string -- any of: string
|
|
// number -- any of: number
|
|
// boolean -- any of: boolean
|
|
// object -- null
|
|
// list -- one or more: , content depends on `option.element.type`, the same as others
|
|
// Map<string, number | string> -- any of: key1, key2, ....
|
|
if option.Kind == tsoptions.CommandLineOptionTypeObject {
|
|
return nil
|
|
}
|
|
|
|
res := &valueCandidate{}
|
|
if option.Kind == tsoptions.CommandLineOptionTypeListOrElement {
|
|
// assert(option.type !== "listOrElement")
|
|
panic("no value candidate for list or element")
|
|
}
|
|
|
|
switch option.Kind {
|
|
case tsoptions.CommandLineOptionTypeString,
|
|
tsoptions.CommandLineOptionTypeNumber,
|
|
tsoptions.CommandLineOptionTypeBoolean:
|
|
res.valueType = diagnostics.X_type_Colon.Format()
|
|
case tsoptions.CommandLineOptionTypeList:
|
|
res.valueType = diagnostics.X_one_or_more_Colon.Format()
|
|
default:
|
|
res.valueType = diagnostics.X_one_of_Colon.Format()
|
|
}
|
|
|
|
res.possibleValues = getPossibleValues(option)
|
|
|
|
return res
|
|
}
|
|
|
|
func getPossibleValues(option *tsoptions.CommandLineOption) string {
|
|
switch option.Kind {
|
|
case tsoptions.CommandLineOptionTypeString,
|
|
tsoptions.CommandLineOptionTypeNumber,
|
|
tsoptions.CommandLineOptionTypeBoolean:
|
|
return string(option.Kind)
|
|
case tsoptions.CommandLineOptionTypeList,
|
|
tsoptions.CommandLineOptionTypeListOrElement:
|
|
return getPossibleValues(option.Elements())
|
|
case tsoptions.CommandLineOptionTypeObject:
|
|
return ""
|
|
default:
|
|
// Map<string, number | string>
|
|
// Group synonyms: es6/es2015
|
|
enumMap := option.EnumMap()
|
|
inverted := collections.NewOrderedMapWithSizeHint[any, []string](enumMap.Size())
|
|
deprecatedKeys := option.DeprecatedKeys()
|
|
|
|
for name, value := range enumMap.Entries() {
|
|
if deprecatedKeys == nil || !deprecatedKeys.Has(name) {
|
|
inverted.Set(value, append(inverted.GetOrZero(value), name))
|
|
}
|
|
}
|
|
var syns []string
|
|
for synonyms := range inverted.Values() {
|
|
syns = append(syns, strings.Join(synonyms, "/"))
|
|
}
|
|
return strings.Join(syns, ", ")
|
|
}
|
|
}
|
|
|
|
func getPrettyOutput(colors *colors, left string, right string, rightAlignOfLeft int, leftAlignOfRight int, terminalWidth int, colorLeft bool) []string {
|
|
// !!! How does terminalWidth interact with UTF-8 encoding? Strada just assumed UTF-16.
|
|
res := make([]string, 0, 4)
|
|
isFirstLine := true
|
|
remainRight := right
|
|
rightCharacterNumber := terminalWidth - leftAlignOfRight
|
|
for len(remainRight) > 0 {
|
|
curLeft := ""
|
|
if isFirstLine {
|
|
curLeft = fmt.Sprintf("%*s", rightAlignOfLeft, left)
|
|
curLeft = fmt.Sprintf("%-*s", leftAlignOfRight, curLeft)
|
|
if colorLeft {
|
|
curLeft = colors.blue(curLeft)
|
|
}
|
|
} else {
|
|
curLeft = strings.Repeat(" ", leftAlignOfRight)
|
|
}
|
|
|
|
idx := min(rightCharacterNumber, len(remainRight))
|
|
curRight := remainRight[:idx]
|
|
remainRight = remainRight[idx:]
|
|
res = append(res, curLeft, curRight, "\n")
|
|
isFirstLine = false
|
|
}
|
|
return res
|
|
}
|
|
|
|
func getDisplayNameTextOfOption(option *tsoptions.CommandLineOption) string {
|
|
return "--" + option.Name + core.IfElse(option.ShortName != "", ", -"+option.ShortName, "")
|
|
}
|