vendor tsgo
This commit is contained in:
200
tools/tsgo/internal/tsoptions/commandlineoption.go
Normal file
200
tools/tsgo/internal/tsoptions/commandlineoption.go
Normal file
@@ -0,0 +1,200 @@
|
||||
package tsoptions
|
||||
|
||||
import (
|
||||
"github.com/microsoft/typescript-go/internal/collections"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/diagnostics"
|
||||
)
|
||||
|
||||
type CommandLineOptionKind string
|
||||
|
||||
const (
|
||||
CommandLineOptionTypeString CommandLineOptionKind = "string"
|
||||
CommandLineOptionTypeNumber CommandLineOptionKind = "number"
|
||||
CommandLineOptionTypeBoolean CommandLineOptionKind = "boolean"
|
||||
CommandLineOptionTypeObject CommandLineOptionKind = "object"
|
||||
CommandLineOptionTypeList CommandLineOptionKind = "list"
|
||||
CommandLineOptionTypeListOrElement CommandLineOptionKind = "listOrElement"
|
||||
CommandLineOptionTypeEnum CommandLineOptionKind = "enum" // map
|
||||
)
|
||||
|
||||
type CommandLineOption struct {
|
||||
Name, ShortName string
|
||||
Kind CommandLineOptionKind
|
||||
|
||||
// used in parsing
|
||||
IsFilePath bool
|
||||
IsTSConfigOnly bool
|
||||
IsCommandLineOnly bool
|
||||
|
||||
// used in output
|
||||
Description *diagnostics.Message
|
||||
DefaultValueDescription any
|
||||
ShowInSimplifiedHelpView bool
|
||||
|
||||
// used in output in serializing and generate tsconfig
|
||||
Category *diagnostics.Message
|
||||
|
||||
// What kind of extra validation `validateJsonOptionValue` should do
|
||||
extraValidation extraValidation
|
||||
|
||||
// checks that option with number type has value >= minValue
|
||||
minValue int
|
||||
|
||||
// true or undefined
|
||||
// used for configDirTemplateSubstitutionOptions
|
||||
allowConfigDirTemplateSubstitution bool
|
||||
|
||||
// used for filter in compilerrunner
|
||||
AffectsDeclarationPath bool
|
||||
AffectsProgramStructure bool
|
||||
AffectsSemanticDiagnostics bool
|
||||
AffectsBuildInfo bool
|
||||
AffectsBindDiagnostics bool
|
||||
AffectsSourceFile bool
|
||||
AffectsModuleResolution bool
|
||||
AffectsEmit bool
|
||||
|
||||
allowJsFlag bool
|
||||
strictFlag bool
|
||||
|
||||
// used in transpileoptions worker
|
||||
// todo: revisit to see if this can be reduced to boolean
|
||||
transpileOptionValue core.Tristate
|
||||
|
||||
// used for CommandLineOptionTypeList
|
||||
listPreserveFalsyValues bool
|
||||
// used for compilerOptionsDeclaration
|
||||
ElementOptions CommandLineOptionNameMap
|
||||
}
|
||||
|
||||
type extraValidation string
|
||||
|
||||
const (
|
||||
extraValidationNone extraValidation = ""
|
||||
extraValidationSpec extraValidation = "spec"
|
||||
extraValidationLocale extraValidation = "locale"
|
||||
)
|
||||
|
||||
func (o *CommandLineOption) DeprecatedKeys() *collections.Set[string] {
|
||||
if o.Kind != CommandLineOptionTypeEnum {
|
||||
return nil
|
||||
}
|
||||
return commandLineOptionDeprecated[o.Name]
|
||||
}
|
||||
|
||||
func (o *CommandLineOption) EnumMap() *collections.OrderedMap[string, any] {
|
||||
if o.Kind != CommandLineOptionTypeEnum {
|
||||
return nil
|
||||
}
|
||||
return commandLineOptionEnumMap[o.Name]
|
||||
}
|
||||
|
||||
func (o *CommandLineOption) Elements() *CommandLineOption {
|
||||
if o.Kind != CommandLineOptionTypeList && o.Kind != CommandLineOptionTypeListOrElement {
|
||||
return nil
|
||||
}
|
||||
return commandLineOptionElements[o.Name]
|
||||
}
|
||||
|
||||
func (o *CommandLineOption) DisallowNullOrUndefined() bool {
|
||||
return o.Name == "extends"
|
||||
}
|
||||
|
||||
// CommandLineOption.Elements()
|
||||
var commandLineOptionElements = map[string]*CommandLineOption{
|
||||
"lib": {
|
||||
Name: "lib",
|
||||
Kind: CommandLineOptionTypeEnum, // libMap,
|
||||
DefaultValueDescription: core.TSUnknown,
|
||||
},
|
||||
"rootDirs": {
|
||||
Name: "rootDirs",
|
||||
Kind: CommandLineOptionTypeString,
|
||||
IsFilePath: true,
|
||||
},
|
||||
"typeRoots": {
|
||||
Name: "typeRoots",
|
||||
Kind: CommandLineOptionTypeString,
|
||||
IsFilePath: true,
|
||||
},
|
||||
"types": {
|
||||
Name: "types",
|
||||
Kind: CommandLineOptionTypeString,
|
||||
},
|
||||
"moduleSuffixes": {
|
||||
Name: "moduleSuffixes",
|
||||
Kind: CommandLineOptionTypeString,
|
||||
},
|
||||
"customConditions": {
|
||||
Name: "condition",
|
||||
Kind: CommandLineOptionTypeString,
|
||||
},
|
||||
"plugins": {
|
||||
Name: "plugin",
|
||||
Kind: CommandLineOptionTypeObject,
|
||||
},
|
||||
// For tsconfig root options
|
||||
"references": {
|
||||
Name: "references",
|
||||
Kind: CommandLineOptionTypeObject,
|
||||
},
|
||||
"files": {
|
||||
Name: "files",
|
||||
Kind: CommandLineOptionTypeString,
|
||||
},
|
||||
"include": {
|
||||
Name: "include",
|
||||
Kind: CommandLineOptionTypeString,
|
||||
},
|
||||
"exclude": {
|
||||
Name: "exclude",
|
||||
Kind: CommandLineOptionTypeString,
|
||||
},
|
||||
"extends": {
|
||||
Name: "extends",
|
||||
Kind: CommandLineOptionTypeString,
|
||||
},
|
||||
// For Watch options
|
||||
"excludeDirectories": {
|
||||
Name: "excludeDirectory",
|
||||
Kind: CommandLineOptionTypeString,
|
||||
IsFilePath: true,
|
||||
extraValidation: extraValidationSpec,
|
||||
},
|
||||
"excludeFiles": {
|
||||
Name: "excludeFile",
|
||||
Kind: CommandLineOptionTypeString,
|
||||
IsFilePath: true,
|
||||
extraValidation: extraValidationSpec,
|
||||
},
|
||||
// Test infra options
|
||||
"libFiles": {
|
||||
Name: "libFiles",
|
||||
Kind: CommandLineOptionTypeString,
|
||||
},
|
||||
}
|
||||
|
||||
// CommandLineOption.EnumMap()
|
||||
var commandLineOptionEnumMap = map[string]*collections.OrderedMap[string, any]{
|
||||
"lib": LibMap,
|
||||
"moduleResolution": moduleResolutionOptionMap,
|
||||
"module": moduleOptionMap,
|
||||
"target": targetOptionMap,
|
||||
"moduleDetection": moduleDetectionOptionMap,
|
||||
"jsx": jsxOptionMap,
|
||||
"newLine": newLineOptionMap,
|
||||
"watchFile": watchFileEnumMap,
|
||||
"watchDirectory": watchDirectoryEnumMap,
|
||||
"fallbackPolling": fallbackEnumMap,
|
||||
}
|
||||
|
||||
// CommandLineOption.DeprecatedKeys()
|
||||
var commandLineOptionDeprecated = map[string]*collections.Set[string]{
|
||||
"module": collections.NewSetFromItems("none", "amd", "system", "umd"),
|
||||
"moduleResolution": collections.NewSetFromItems("node", "classic", "node10"),
|
||||
"target": collections.NewSetFromItems("es5"),
|
||||
}
|
||||
|
||||
// todo: revisit to see if this can be improved
|
||||
type CompilerOptionsValue any
|
||||
403
tools/tsgo/internal/tsoptions/commandlineparser.go
Normal file
403
tools/tsgo/internal/tsoptions/commandlineparser.go
Normal file
@@ -0,0 +1,403 @@
|
||||
package tsoptions
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"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/stringutil"
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
"github.com/microsoft/typescript-go/internal/vfs"
|
||||
)
|
||||
|
||||
func (p *commandLineParser) AlternateMode() *AlternateModeDiagnostics {
|
||||
return p.workerDiagnostics.didYouMean.alternateMode
|
||||
}
|
||||
|
||||
func (p *commandLineParser) OptionsDeclarations() []*CommandLineOption {
|
||||
return p.workerDiagnostics.didYouMean.OptionDeclarations
|
||||
}
|
||||
|
||||
func (p *commandLineParser) UnknownOptionDiagnostic() *diagnostics.Message {
|
||||
return p.workerDiagnostics.didYouMean.UnknownOptionDiagnostic
|
||||
}
|
||||
|
||||
func (p *commandLineParser) UnknownDidYouMeanDiagnostic() *diagnostics.Message {
|
||||
return p.workerDiagnostics.didYouMean.UnknownDidYouMeanDiagnostic
|
||||
}
|
||||
|
||||
type commandLineParser struct {
|
||||
workerDiagnostics *ParseCommandLineWorkerDiagnostics
|
||||
optionsMap *NameMap
|
||||
fs vfs.FS
|
||||
currentDirectory string
|
||||
options *collections.OrderedMap[string, any]
|
||||
fileNames []string
|
||||
errors []*ast.Diagnostic
|
||||
}
|
||||
|
||||
func ParseCommandLine(
|
||||
commandLine []string,
|
||||
host ParseConfigHost,
|
||||
) *ParsedCommandLine {
|
||||
if commandLine == nil {
|
||||
commandLine = []string{}
|
||||
}
|
||||
parser := parseCommandLineWorker(CompilerOptionsDidYouMeanDiagnostics, commandLine, host.FS(), 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{
|
||||
UseCaseSensitiveFileNames: host.FS().UseCaseSensitiveFileNames(),
|
||||
CurrentDirectory: host.GetCurrentDirectory(),
|
||||
})
|
||||
result.ParsedConfig.WatchOptions = watchOptions
|
||||
result.Errors = parser.errors
|
||||
result.Raw = parser.options
|
||||
return result
|
||||
}
|
||||
|
||||
func ParseBuildCommandLine(
|
||||
commandLine []string,
|
||||
host ParseConfigHost,
|
||||
) *ParsedBuildCommandLine {
|
||||
if commandLine == nil {
|
||||
commandLine = []string{}
|
||||
}
|
||||
parser := parseCommandLineWorker(buildOptionsDidYouMeanDiagnostics, commandLine, host.FS(), host.GetCurrentDirectory())
|
||||
compilerOptions := &core.CompilerOptions{}
|
||||
for key, value := range parser.options.Entries() {
|
||||
buildOption := BuildNameMap.Get(key)
|
||||
if buildOption == &TscBuildOption || buildOption == CompilerNameMap.Get(key) {
|
||||
ParseCompilerOptions(key, value, compilerOptions)
|
||||
}
|
||||
}
|
||||
result := &ParsedBuildCommandLine{
|
||||
BuildOptions: convertMapToOptions(parser.options, &buildOptionsParser{&core.BuildOptions{}}).BuildOptions,
|
||||
CompilerOptions: compilerOptions,
|
||||
WatchOptions: convertMapToOptions(parser.options, &watchOptionsParser{&core.WatchOptions{}}).WatchOptions,
|
||||
Projects: parser.fileNames,
|
||||
Errors: parser.errors,
|
||||
Raw: parser.options,
|
||||
|
||||
comparePathsOptions: tspath.ComparePathsOptions{
|
||||
UseCaseSensitiveFileNames: host.FS().UseCaseSensitiveFileNames(),
|
||||
CurrentDirectory: host.GetCurrentDirectory(),
|
||||
},
|
||||
}
|
||||
|
||||
if len(result.Projects) == 0 {
|
||||
// tsc -b invoked with no extra arguments; act as if invoked with "tsc -b ."
|
||||
result.Projects = append(result.Projects, ".")
|
||||
}
|
||||
|
||||
// Nonsensical combinations
|
||||
if result.BuildOptions.Clean.IsTrue() && result.BuildOptions.Force.IsTrue() {
|
||||
result.Errors = append(result.Errors, ast.NewCompilerDiagnostic(diagnostics.Options_0_and_1_cannot_be_combined, "clean", "force"))
|
||||
}
|
||||
if result.BuildOptions.Clean.IsTrue() && result.BuildOptions.Verbose.IsTrue() {
|
||||
result.Errors = append(result.Errors, ast.NewCompilerDiagnostic(diagnostics.Options_0_and_1_cannot_be_combined, "clean", "verbose"))
|
||||
}
|
||||
if result.BuildOptions.Clean.IsTrue() && result.CompilerOptions.Watch.IsTrue() {
|
||||
result.Errors = append(result.Errors, ast.NewCompilerDiagnostic(diagnostics.Options_0_and_1_cannot_be_combined, "clean", "watch"))
|
||||
}
|
||||
if result.CompilerOptions.Watch.IsTrue() && result.BuildOptions.Dry.IsTrue() {
|
||||
result.Errors = append(result.Errors, ast.NewCompilerDiagnostic(diagnostics.Options_0_and_1_cannot_be_combined, "watch", "dry"))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func parseCommandLineWorker(
|
||||
parseCommandLineWithDiagnostics *ParseCommandLineWorkerDiagnostics,
|
||||
commandLine []string,
|
||||
fs vfs.FS,
|
||||
currentDirectory string,
|
||||
) *commandLineParser {
|
||||
parser := &commandLineParser{
|
||||
fs: fs,
|
||||
currentDirectory: currentDirectory,
|
||||
workerDiagnostics: parseCommandLineWithDiagnostics,
|
||||
fileNames: []string{},
|
||||
options: &collections.OrderedMap[string, any]{},
|
||||
errors: []*ast.Diagnostic{},
|
||||
}
|
||||
parser.optionsMap = GetNameMapFromList(parser.OptionsDeclarations())
|
||||
parser.parseStrings(commandLine)
|
||||
return parser
|
||||
}
|
||||
|
||||
func (p *commandLineParser) parseStrings(args []string) {
|
||||
i := 0
|
||||
for i < len(args) {
|
||||
s := args[i]
|
||||
i++
|
||||
if s == "" {
|
||||
continue
|
||||
}
|
||||
switch s[0] {
|
||||
case '@':
|
||||
p.parseResponseFile(s[1:])
|
||||
case '-':
|
||||
inputOptionName := getInputOptionName(s)
|
||||
opt := p.optionsMap.GetOptionDeclarationFromName(inputOptionName, true /*allowShort*/)
|
||||
if opt != nil {
|
||||
i = p.parseOptionValue(args, i, opt, p.workerDiagnostics.OptionTypeMismatchDiagnostic)
|
||||
} else {
|
||||
watchOpt := WatchNameMap.GetOptionDeclarationFromName(inputOptionName, true /*allowShort*/)
|
||||
if watchOpt != nil {
|
||||
i = p.parseOptionValue(args, i, watchOpt, watchOptionsDidYouMeanDiagnostics.OptionTypeMismatchDiagnostic)
|
||||
} else {
|
||||
p.errors = append(p.errors, p.createUnknownOptionError(inputOptionName, s, nil, nil))
|
||||
}
|
||||
}
|
||||
default:
|
||||
p.fileNames = append(p.fileNames, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getInputOptionName(input string) string {
|
||||
// removes at most two leading '-' from the input string
|
||||
return strings.TrimPrefix(strings.TrimPrefix(input, "-"), "-")
|
||||
}
|
||||
|
||||
func (p *commandLineParser) parseResponseFile(fileName string) {
|
||||
fileName = tspath.GetNormalizedAbsolutePath(fileName, p.currentDirectory)
|
||||
fileContents, errors := tryReadFile(fileName, func(fileName string) (string, bool) {
|
||||
if p.fs == nil {
|
||||
return "", false
|
||||
}
|
||||
read, err := p.fs.ReadFile(fileName)
|
||||
return read, err
|
||||
}, p.errors)
|
||||
p.errors = errors
|
||||
|
||||
if fileContents == "" {
|
||||
return
|
||||
}
|
||||
|
||||
var args []string
|
||||
text := []rune(fileContents)
|
||||
textLength := len(text)
|
||||
pos := 0
|
||||
for pos < textLength {
|
||||
for pos < textLength && text[pos] <= ' ' {
|
||||
pos++
|
||||
}
|
||||
if pos >= textLength {
|
||||
break
|
||||
}
|
||||
start := pos
|
||||
if text[pos] == '"' {
|
||||
pos++
|
||||
for pos < textLength && text[pos] != '"' {
|
||||
pos++
|
||||
}
|
||||
if pos < textLength {
|
||||
args = append(args, string(text[start+1:pos]))
|
||||
pos++
|
||||
} else {
|
||||
p.errors = append(p.errors, ast.NewCompilerDiagnostic(diagnostics.Unterminated_quoted_string_in_response_file_0, fileName))
|
||||
}
|
||||
} else {
|
||||
for text[pos] > ' ' {
|
||||
pos++
|
||||
}
|
||||
args = append(args, string(text[start:pos]))
|
||||
}
|
||||
}
|
||||
p.parseStrings(args)
|
||||
}
|
||||
|
||||
func tryReadFile(fileName string, readFile func(string) (string, bool), errors []*ast.Diagnostic) (string, []*ast.Diagnostic) {
|
||||
// this function adds a compiler diagnostic if the file cannot be read
|
||||
text, e := readFile(fileName)
|
||||
|
||||
if !e {
|
||||
// !!! Divergence: the returned error will not give a useful message
|
||||
// errors = append(errors, ast.NewCompilerDiagnostic(diagnostics.Cannot_read_file_0_Colon_1, *e));
|
||||
text = ""
|
||||
errors = append(errors, ast.NewCompilerDiagnostic(diagnostics.Cannot_read_file_0, fileName))
|
||||
}
|
||||
return text, errors
|
||||
}
|
||||
|
||||
func (p *commandLineParser) parseOptionValue(
|
||||
args []string,
|
||||
i int,
|
||||
opt *CommandLineOption,
|
||||
diag *diagnostics.Message,
|
||||
) int {
|
||||
if opt.IsTSConfigOnly && i <= len(args) {
|
||||
optValue := ""
|
||||
if i < len(args) {
|
||||
optValue = args[i]
|
||||
}
|
||||
if optValue == "null" {
|
||||
p.options.Set(opt.Name, nil)
|
||||
i++
|
||||
} else if opt.Kind == "boolean" {
|
||||
if optValue == "false" {
|
||||
p.options.Set(opt.Name, false)
|
||||
i++
|
||||
} else {
|
||||
if optValue == "true" {
|
||||
i++
|
||||
}
|
||||
p.errors = append(p.errors, ast.NewCompilerDiagnostic(diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file_or_set_to_false_or_null_on_command_line, opt.Name))
|
||||
}
|
||||
} else {
|
||||
p.errors = append(p.errors, ast.NewCompilerDiagnostic(diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file_or_set_to_null_on_command_line, opt.Name))
|
||||
if len(optValue) != 0 && !strings.HasPrefix(optValue, "-") {
|
||||
i++
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Check to see if no argument was provided (e.g. "--locale" is the last command-line argument).
|
||||
if i >= len(args) {
|
||||
if opt.Kind != "boolean" {
|
||||
p.errors = append(p.errors, ast.NewCompilerDiagnostic(diag, opt.Name, getCompilerOptionValueTypeString(opt)))
|
||||
if opt.Kind == "list" {
|
||||
p.options.Set(opt.Name, []string{})
|
||||
} else if opt.Kind == "enum" {
|
||||
p.errors = append(p.errors, createDiagnosticForInvalidEnumType(opt, nil, nil))
|
||||
}
|
||||
} else {
|
||||
p.options.Set(opt.Name, true)
|
||||
}
|
||||
return i
|
||||
}
|
||||
if args[i] != "null" {
|
||||
switch opt.Kind {
|
||||
case "number":
|
||||
// !!! Make sure this parseInt matches JS parseInt
|
||||
num, e := strconv.Atoi(args[i])
|
||||
if e == nil {
|
||||
if num >= opt.minValue {
|
||||
p.options.Set(opt.Name, num)
|
||||
} else {
|
||||
p.errors = append(p.errors, ast.NewCompilerDiagnostic(diagnostics.Option_0_requires_value_to_be_greater_than_1, opt.Name, strconv.Itoa(opt.minValue)))
|
||||
}
|
||||
} else {
|
||||
p.errors = append(p.errors, ast.NewCompilerDiagnostic(diag, opt.Name, "number"))
|
||||
}
|
||||
i++
|
||||
case "boolean":
|
||||
// boolean flag has optional value true, false, others
|
||||
optValue := args[i]
|
||||
|
||||
// check next argument as boolean flag value
|
||||
if optValue == "false" {
|
||||
p.options.Set(opt.Name, false)
|
||||
} else {
|
||||
p.options.Set(opt.Name, true)
|
||||
}
|
||||
// try to consume next argument as value for boolean flag; do not consume argument if it is not "true" or "false"
|
||||
if optValue == "false" || optValue == "true" {
|
||||
i++
|
||||
}
|
||||
case "string":
|
||||
val, err := validateJsonOptionValue(opt, args[i], nil, nil)
|
||||
if err == nil {
|
||||
p.options.Set(opt.Name, val)
|
||||
} else {
|
||||
p.errors = append(p.errors, err...)
|
||||
}
|
||||
i++
|
||||
case "list":
|
||||
result, err := p.parseListTypeOption(opt, args[i])
|
||||
p.options.Set(opt.Name, result)
|
||||
p.errors = append(p.errors, err...)
|
||||
if len(result) > 0 || len(err) > 0 {
|
||||
i++
|
||||
}
|
||||
case "listOrElement":
|
||||
// If not a primitive, the possible types are specified in what is effectively a map of options.
|
||||
panic("listOrElement not supported here")
|
||||
default:
|
||||
val, err := convertJsonOptionOfEnumType(opt, strings.TrimFunc(args[i], stringutil.IsWhiteSpaceLike), nil, nil)
|
||||
p.options.Set(opt.Name, val)
|
||||
p.errors = append(p.errors, err...)
|
||||
i++
|
||||
}
|
||||
} else {
|
||||
p.options.Set(opt.Name, nil)
|
||||
i++
|
||||
}
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func (p *commandLineParser) parseListTypeOption(opt *CommandLineOption, value string) ([]any, []*ast.Diagnostic) {
|
||||
return ParseListTypeOption(opt, value)
|
||||
}
|
||||
|
||||
func ParseListTypeOption(opt *CommandLineOption, value string) ([]any, []*ast.Diagnostic) {
|
||||
value = strings.TrimSpace(value)
|
||||
var errors []*ast.Diagnostic
|
||||
if strings.HasPrefix(value, "-") {
|
||||
return []any{}, errors
|
||||
}
|
||||
if opt.Kind == "listOrElement" && !strings.ContainsRune(value, ',') {
|
||||
val, err := validateJsonOptionValue(opt, value, nil, nil)
|
||||
if err != nil {
|
||||
return []any{}, err
|
||||
}
|
||||
return []any{val.(string)}, errors
|
||||
}
|
||||
if value == "" {
|
||||
return []any{}, errors
|
||||
}
|
||||
values := strings.Split(value, ",")
|
||||
switch opt.Elements().Kind {
|
||||
case "string":
|
||||
elements := core.MapFiltered(values, func(v string) (any, bool) {
|
||||
val, err := validateJsonOptionValue(opt.Elements(), v, nil, nil)
|
||||
if s, ok := val.(string); ok && len(err) == 0 && s != "" {
|
||||
return s, true
|
||||
}
|
||||
errors = append(errors, err...)
|
||||
return "", false
|
||||
})
|
||||
return elements, errors
|
||||
case "boolean", "object", "number":
|
||||
// do nothing: only string and enum/object types currently allowed as list entries
|
||||
// !!! we don't actually have number list options, so I didn't implement number list parsing
|
||||
panic("List of " + opt.Elements().Kind + " is not yet supported.")
|
||||
default:
|
||||
result := core.MapFiltered(values, func(v string) (any, bool) {
|
||||
val, err := convertJsonOptionOfEnumType(opt.Elements(), strings.TrimFunc(v, stringutil.IsWhiteSpaceLike), nil, nil)
|
||||
if s, ok := val.(string); ok && len(err) == 0 && s != "" {
|
||||
return s, true
|
||||
}
|
||||
errors = append(errors, err...)
|
||||
return "", false
|
||||
})
|
||||
return result, errors
|
||||
}
|
||||
}
|
||||
|
||||
func convertJsonOptionOfEnumType(
|
||||
opt *CommandLineOption,
|
||||
value string,
|
||||
valueExpression *ast.Expression,
|
||||
sourceFile *ast.SourceFile,
|
||||
) (any, []*ast.Diagnostic) {
|
||||
if value == "" {
|
||||
return nil, nil
|
||||
}
|
||||
key := strings.ToLower(value)
|
||||
typeMap := opt.EnumMap()
|
||||
if typeMap == nil {
|
||||
return nil, nil
|
||||
}
|
||||
val, ok := typeMap.Get(key)
|
||||
if ok {
|
||||
return validateJsonOptionValue(opt, val, valueExpression, sourceFile)
|
||||
}
|
||||
return nil, []*ast.Diagnostic{createDiagnosticForInvalidEnumType(opt, sourceFile, valueExpression)}
|
||||
}
|
||||
572
tools/tsgo/internal/tsoptions/commandlineparser_test.go
Normal file
572
tools/tsgo/internal/tsoptions/commandlineparser_test.go
Normal file
@@ -0,0 +1,572 @@
|
||||
package tsoptions_test
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"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/diagnosticwriter"
|
||||
"github.com/microsoft/typescript-go/internal/json"
|
||||
"github.com/microsoft/typescript-go/internal/repo"
|
||||
"github.com/microsoft/typescript-go/internal/testutil/baseline"
|
||||
"github.com/microsoft/typescript-go/internal/testutil/filefixture"
|
||||
"github.com/microsoft/typescript-go/internal/tsoptions"
|
||||
"github.com/microsoft/typescript-go/internal/tsoptions/tsoptionstest"
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
"github.com/microsoft/typescript-go/internal/vfs/osvfs"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestCommandLineParseResult(t *testing.T) {
|
||||
t.Parallel()
|
||||
repo.SkipIfNoTypeScriptSubmodule(t)
|
||||
|
||||
parseCommandLineSubScenarios := []*subScenarioInput{
|
||||
// --lib es6 0.ts
|
||||
{"Parse single option of library flag", []string{"--lib", "es6", "0.ts"}},
|
||||
{"Handles may only be used with --build flags", []string{"--build", "--clean", "--dry", "--force", "--verbose"}},
|
||||
// --declarations --allowTS
|
||||
{"Handles did you mean for misspelt flags", []string{"--declarations", "--allowTS"}},
|
||||
// --lib es5,es2015.symbol.wellknown 0.ts
|
||||
{"Parse multiple options of library flags", []string{"--lib", "es5,es2015.symbol.wellknown", "0.ts"}},
|
||||
// --lib es5,invalidOption 0.ts
|
||||
{"Parse invalid option of library flags", []string{"--lib", "es5,invalidOption", "0.ts"}},
|
||||
// 0.ts --jsx
|
||||
{"Parse empty options of --jsx", []string{"0.ts", "--jsx"}},
|
||||
// 0.ts --
|
||||
{"Parse empty options of --module", []string{"0.ts", "--module"}},
|
||||
// 0.ts --newLine
|
||||
{"Parse empty options of --newLine", []string{"0.ts", "--newLine"}},
|
||||
// 0.ts --target
|
||||
{"Parse empty options of --target", []string{"0.ts", "--target"}},
|
||||
// 0.ts --moduleResolution
|
||||
{"Parse empty options of --moduleResolution", []string{"0.ts", "--moduleResolution"}},
|
||||
// 0.ts --lib
|
||||
{"Parse empty options of --lib", []string{"0.ts", "--lib"}},
|
||||
// 0.ts --lib
|
||||
// This test is an error because the empty string is falsey
|
||||
{"Parse empty string of --lib", []string{"0.ts", "--lib", ""}},
|
||||
// 0.ts --lib
|
||||
{"Parse immediately following command line argument of --lib", []string{"0.ts", "--lib", "--sourcemap"}},
|
||||
// --lib es5, es7 0.ts
|
||||
{"Parse --lib option with extra comma", []string{"--lib", "es5,", "es7", "0.ts"}},
|
||||
// --lib es5, es7 0.ts
|
||||
{"Parse --lib option with trailing white-space", []string{"--lib", "es5, ", "es7", "0.ts"}},
|
||||
// --lib es5,es2015.symbol.wellknown --target es5 0.ts
|
||||
{"Parse multiple compiler flags with input files at the end", []string{"--lib", "es5,es2015.symbol.wellknown", "--target", "es5", "0.ts"}},
|
||||
// --module commonjs --target es5 0.ts --lib es5,es2015.symbol.wellknown
|
||||
{"Parse multiple compiler flags with input files in the middle", []string{"--module", "commonjs", "--target", "es5", "0.ts", "--lib", "es5,es2015.symbol.wellknown"}},
|
||||
// --module commonjs --target es5 --lib es5 0.ts --library es2015.array,es2015.symbol.wellknown
|
||||
{"Parse multiple library compiler flags ", []string{"--module", "commonjs", "--target", "es5", "--lib", "es5", "0.ts", "--lib", "es2015.core, es2015.symbol.wellknown "}},
|
||||
{"Parse explicit boolean flag value", []string{"--strictNullChecks", "false", "0.ts"}},
|
||||
{"Parse non boolean argument after boolean flag", []string{"--noImplicitAny", "t", "0.ts"}},
|
||||
{"Parse implicit boolean flag value", []string{"--strictNullChecks"}},
|
||||
{"parse --incremental", []string{"--incremental", "0.ts"}},
|
||||
{"parse --tsBuildInfoFile", []string{"--tsBuildInfoFile", "build.tsbuildinfo", "0.ts"}},
|
||||
{"allows tsconfig only option to be set to null", []string{"--composite", "null", "-tsBuildInfoFile", "null", "0.ts"}},
|
||||
|
||||
// ****** Watch Options ******
|
||||
{"parse --watchFile", []string{"--watchFile", "UseFsEvents", "0.ts"}},
|
||||
{"parse --watchDirectory", []string{"--watchDirectory", "FixedPollingInterval", "0.ts"}},
|
||||
{"parse --fallbackPolling", []string{"--fallbackPolling", "PriorityInterval", "0.ts"}},
|
||||
{"parse --synchronousWatchDirectory", []string{"--synchronousWatchDirectory", "0.ts"}},
|
||||
{"errors on missing argument to --fallbackPolling", []string{"0.ts", "--fallbackPolling"}},
|
||||
{"parse --excludeDirectories", []string{"--excludeDirectories", "**/temp", "0.ts"}},
|
||||
{"errors on invalid excludeDirectories", []string{"--excludeDirectories", "**/../*", "0.ts"}},
|
||||
{"parse --excludeFiles", []string{"--excludeFiles", "**/temp/*.ts", "0.ts"}},
|
||||
{"errors on invalid excludeFiles", []string{"--excludeFiles", "**/../*", "0.ts"}},
|
||||
}
|
||||
|
||||
for _, testCase := range parseCommandLineSubScenarios {
|
||||
testCase.createSubScenario("parseCommandLine").assertParseResult(t)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResponseFileDoesNotPanic(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Passing `@` with an empty or relative filename should not panic.
|
||||
// It should produce a diagnostic error instead.
|
||||
cwd := t.TempDir()
|
||||
t.Run("empty response file", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
parsed := tsoptions.ParseCommandLineTestWorker(nil, []string{"@"}, osvfs.FS(), cwd)
|
||||
assert.Assert(t, len(parsed.Errors) > 0, "expected an error for empty response file name")
|
||||
})
|
||||
|
||||
t.Run("relative response file", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
parsed := tsoptions.ParseCommandLineTestWorker(nil, []string{"@blah"}, osvfs.FS(), cwd)
|
||||
assert.Assert(t, len(parsed.Errors) > 0, "expected an error for non-existent response file")
|
||||
})
|
||||
}
|
||||
|
||||
func TestParseCommandLineTypeRootsRelativePath(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
host := tsoptionstest.NewVFSParseConfigHost(map[string]string{
|
||||
"/home/project/bug.ts": `let x = 1;`,
|
||||
}, "/home/project", true)
|
||||
|
||||
cmdLine := tsoptions.ParseCommandLine([]string{"--typeRoots", "t", "bug.ts"}, host)
|
||||
|
||||
typeRoots := cmdLine.CompilerOptions().TypeRoots
|
||||
assert.Assert(t, typeRoots != nil, "typeRoots should not be nil")
|
||||
assert.Equal(t, len(typeRoots), 1)
|
||||
assert.Assert(t, tspath.IsRootedDiskPath(typeRoots[0]), "typeRoots entry should be an absolute path, got: %s", typeRoots[0])
|
||||
assert.Assert(t, strings.HasSuffix(typeRoots[0], "/t"), "typeRoots entry should end with '/t', got: %s", typeRoots[0])
|
||||
}
|
||||
|
||||
func TestCustomConditionsNullOverride(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := map[string]string{
|
||||
"/project/tsconfig.json": `{
|
||||
"compilerOptions": {
|
||||
"customConditions": ["condition1", "condition2"]
|
||||
}
|
||||
}`,
|
||||
"/project/index.ts": `console.log("Hello, World!");`,
|
||||
}
|
||||
|
||||
host := tsoptionstest.NewVFSParseConfigHost(files, "/project", true)
|
||||
|
||||
// Parse command line with --customConditions null
|
||||
cmdLine := tsoptions.ParseCommandLine([]string{"--project", "/project", "--customConditions", "null"}, host)
|
||||
|
||||
// Check that the raw options contain null for customConditions
|
||||
if rawMap, ok := cmdLine.Raw.(*collections.OrderedMap[string, any]); ok {
|
||||
customConditionsRaw, exists := rawMap.Get("customConditions")
|
||||
assert.Assert(t, exists, "customConditions should exist in raw options")
|
||||
assert.Assert(t, customConditionsRaw == nil, "customConditions should be nil in raw options, got: %v", customConditionsRaw)
|
||||
} else {
|
||||
t.Fatal("Raw options should be an OrderedMap")
|
||||
}
|
||||
|
||||
// Now parse the config file with the command line options
|
||||
// Wrap command line options in "compilerOptions" key to match tsconfig.json structure
|
||||
wrappedRaw := &collections.OrderedMap[string, any]{}
|
||||
wrappedRaw.Set("compilerOptions", cmdLine.Raw.(*collections.OrderedMap[string, any]))
|
||||
parsedConfig, errors := tsoptions.GetParsedCommandLineOfConfigFile(
|
||||
"/project/tsconfig.json",
|
||||
cmdLine.CompilerOptions(),
|
||||
wrappedRaw,
|
||||
host,
|
||||
nil,
|
||||
)
|
||||
|
||||
assert.Assert(t, len(errors) == 0, "Should not have errors: %v", errors)
|
||||
|
||||
// Check that customConditions is nil (overridden by command line)
|
||||
customConditions := parsedConfig.CompilerOptions().CustomConditions
|
||||
assert.Assert(t, customConditions == nil, "customConditions should be nil after override, got: %v", customConditions)
|
||||
}
|
||||
|
||||
func TestParseCommandLineVerifyNull(t *testing.T) {
|
||||
t.Parallel()
|
||||
repo.SkipIfNoTypeScriptSubmodule(t)
|
||||
|
||||
// run test for boolean
|
||||
subScenarioInput{"allows setting option type boolean to false", []string{"--composite", "false", "0.ts"}}.createSubScenario("parseCommandLine").assertParseResult(t)
|
||||
|
||||
verifyNullSubScenarios := []verifyNull{
|
||||
{
|
||||
subScenario: "option of type boolean",
|
||||
optionName: "composite",
|
||||
nonNullValue: "true",
|
||||
},
|
||||
{
|
||||
subScenario: "option of type object",
|
||||
optionName: "paths",
|
||||
},
|
||||
{
|
||||
subScenario: "option of type list",
|
||||
optionName: "rootDirs",
|
||||
nonNullValue: "abc,xyz",
|
||||
},
|
||||
createVerifyNullForNonNullIncluded("option of type string", tsoptions.CommandLineOptionTypeString, "hello"),
|
||||
createVerifyNullForNonNullIncluded("option of type number", tsoptions.CommandLineOptionTypeNumber, "10"),
|
||||
// todo: make the following work for tests -- currently it is difficult to do extra options of enum type
|
||||
// createVerifyNullForNonNullIncluded("option of type custom map", CommandLineOptionTypeEnum, "node"),
|
||||
}
|
||||
|
||||
for _, verifyNullCase := range verifyNullSubScenarios {
|
||||
createSubScenario(
|
||||
"parseCommandLine",
|
||||
verifyNullCase.subScenario+" allows setting it to null",
|
||||
[]string{"--" + verifyNullCase.optionName, "null", "0.ts"},
|
||||
verifyNullCase.optDecls,
|
||||
).assertParseResult(t)
|
||||
|
||||
if verifyNullCase.nonNullValue != "" {
|
||||
createSubScenario(
|
||||
"parseCommandLine",
|
||||
verifyNullCase.subScenario+" errors if non null value is passed",
|
||||
[]string{"--" + verifyNullCase.optionName, verifyNullCase.nonNullValue, "0.ts"},
|
||||
verifyNullCase.optDecls,
|
||||
).assertParseResult(t)
|
||||
}
|
||||
|
||||
createSubScenario(
|
||||
"parseCommandLine",
|
||||
verifyNullCase.subScenario+" errors if its followed by another option",
|
||||
[]string{"0.ts", "--strictNullChecks", "--" + verifyNullCase.optionName},
|
||||
verifyNullCase.optDecls,
|
||||
).assertParseResult(t)
|
||||
|
||||
createSubScenario(
|
||||
"parseCommandLine",
|
||||
verifyNullCase.subScenario+" errors if its last option",
|
||||
[]string{"0.ts", "--" + verifyNullCase.optionName},
|
||||
verifyNullCase.optDecls,
|
||||
).assertParseResult(t)
|
||||
}
|
||||
}
|
||||
|
||||
func createVerifyNullForNonNullIncluded(subScenario string, kind tsoptions.CommandLineOptionKind, nonNullValue string) verifyNull {
|
||||
return verifyNull{
|
||||
subScenario: subScenario,
|
||||
optionName: "optionName",
|
||||
nonNullValue: nonNullValue,
|
||||
optDecls: slices.Concat(tsoptions.OptionsDeclarations, []*tsoptions.CommandLineOption{{
|
||||
Name: "optionName",
|
||||
Kind: kind,
|
||||
IsTSConfigOnly: true,
|
||||
Category: diagnostics.Backwards_Compatibility,
|
||||
Description: diagnostics.Enable_project_compilation,
|
||||
DefaultValueDescription: nil,
|
||||
}}),
|
||||
}
|
||||
}
|
||||
|
||||
func (f commandLineSubScenario) assertParseResult(t *testing.T) {
|
||||
t.Helper()
|
||||
t.Run(f.testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
originalBaseline := f.baseline.ReadFile(t)
|
||||
tsBaseline := parseExistingCompilerBaseline(t, originalBaseline)
|
||||
|
||||
// f.workerDiagnostic is either defined or set to default pointer in `createSubScenario`
|
||||
parsed := tsoptions.ParseCommandLineTestWorker(f.optDecls, f.commandLine, osvfs.FS(), t.TempDir())
|
||||
|
||||
newBaselineFileNames := strings.Join(parsed.FileNames, ",")
|
||||
assert.Equal(t, tsBaseline.fileNames, newBaselineFileNames)
|
||||
|
||||
o, _ := json.Marshal(parsed.Options)
|
||||
newParsedCompilerOptions := &core.CompilerOptions{}
|
||||
e := json.Unmarshal(o, newParsedCompilerOptions)
|
||||
assert.NilError(t, e)
|
||||
assert.DeepEqual(t, tsBaseline.options, newParsedCompilerOptions, cmpopts.IgnoreUnexported(core.CompilerOptions{}))
|
||||
|
||||
newParsedWatchOptions := core.WatchOptions{}
|
||||
e = json.Unmarshal(o, &newParsedWatchOptions)
|
||||
assert.NilError(t, e)
|
||||
|
||||
// !!! useful for debugging but will not pass due to `none` as enum options
|
||||
// assert.DeepEqual(t, tsBaseline.watchoptions, newParsedWatchOptions)
|
||||
|
||||
var formattedErrors strings.Builder
|
||||
diagnosticwriter.WriteFormatDiagnostics(&formattedErrors, diagnosticwriter.FromASTDiagnostics(parsed.Errors), &diagnosticwriter.FormattingOptions{NewLine: "\n"})
|
||||
newBaselineErrors := formattedErrors.String()
|
||||
|
||||
// !!!
|
||||
// useful for debugging--compares the new errors with the old errors. currently will NOT pass because of unimplemented options, not completely identical enum options, etc
|
||||
// assert.Equal(t, tsBaseline.errors, newBaselineErrors)
|
||||
|
||||
baseline.Run(t, f.testName+".js", formatNewBaseline(f.commandLine, o, newBaselineFileNames, newBaselineErrors), baseline.Options{Subfolder: "tsoptions/commandLineParsing"})
|
||||
})
|
||||
}
|
||||
|
||||
func parseExistingCompilerBaseline(t *testing.T, baseline string) *TestCommandLineParser {
|
||||
_, rest, _ := strings.Cut(baseline, "CompilerOptions::\n")
|
||||
compilerOptions, rest, watchFound := strings.Cut(rest, "\nWatchOptions::\n")
|
||||
watchOptions, rest, _ := strings.Cut(rest, "\nFileNames::\n")
|
||||
fileNames, errors, _ := strings.Cut(rest, "\nErrors::\n")
|
||||
|
||||
baselineCompilerOptions := &core.CompilerOptions{}
|
||||
e := json.Unmarshal([]byte(compilerOptions), &baselineCompilerOptions)
|
||||
assert.NilError(t, e)
|
||||
|
||||
baselineWatchOptions := &core.WatchOptions{}
|
||||
if watchFound && watchOptions != "" {
|
||||
e2 := json.Unmarshal([]byte(watchOptions), &baselineWatchOptions)
|
||||
assert.NilError(t, e2)
|
||||
}
|
||||
|
||||
return &TestCommandLineParser{
|
||||
options: baselineCompilerOptions,
|
||||
watchoptions: baselineWatchOptions,
|
||||
fileNames: fileNames,
|
||||
errors: errors,
|
||||
}
|
||||
}
|
||||
|
||||
func formatNewBaseline(
|
||||
commandLine []string,
|
||||
opts []byte,
|
||||
fileNames string,
|
||||
errors string,
|
||||
) string {
|
||||
var formatted strings.Builder
|
||||
formatted.WriteString("Args::\n")
|
||||
formatted.WriteByte('[')
|
||||
for i, arg := range commandLine {
|
||||
if i > 0 {
|
||||
formatted.WriteString(", ")
|
||||
}
|
||||
formatted.WriteByte('"')
|
||||
formatted.WriteString(arg)
|
||||
formatted.WriteByte('"')
|
||||
}
|
||||
formatted.WriteByte(']')
|
||||
formatted.WriteString("\n\nCompilerOptions::\n")
|
||||
formatted.Write(opts)
|
||||
// todo: watch options not implemented
|
||||
// formatted.WriteString("WatchOptions::\n")
|
||||
formatted.WriteString("\n\nFileNames::\n")
|
||||
formatted.WriteString(fileNames)
|
||||
formatted.WriteString("\n\nErrors::\n")
|
||||
formatted.WriteString(errors)
|
||||
return formatted.String()
|
||||
}
|
||||
|
||||
func (f commandLineSubScenario) assertBuildParseResult(t *testing.T) {
|
||||
t.Helper()
|
||||
f.assertBuildParseResultWithTsBaseline(t, func() *TestCommandLineParserBuild {
|
||||
originalBaseline := f.baseline.ReadFile(t)
|
||||
return parseExistingCompilerBaselineBuild(t, originalBaseline)
|
||||
})
|
||||
}
|
||||
|
||||
func (f commandLineSubScenario) assertBuildParseResultWithTsBaseline(t *testing.T, getTsBaseline func() *TestCommandLineParserBuild) {
|
||||
t.Helper()
|
||||
t.Run(f.testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var tsBaseline *TestCommandLineParserBuild
|
||||
if getTsBaseline != nil {
|
||||
tsBaseline = getTsBaseline()
|
||||
}
|
||||
|
||||
// f.workerDiagnostic is either defined or set to default pointer in `createSubScenario`
|
||||
parsed := tsoptions.ParseBuildCommandLine(f.commandLine, &tsoptionstest.VfsParseConfigHost{
|
||||
Vfs: osvfs.FS(),
|
||||
CurrentDirectory: tspath.NormalizeSlashes(repo.TypeScriptSubmodulePath()),
|
||||
})
|
||||
|
||||
newBaselineProjects := strings.Join(parsed.Projects, ",")
|
||||
if getTsBaseline != nil {
|
||||
assert.Equal(t, tsBaseline.projects, newBaselineProjects)
|
||||
}
|
||||
|
||||
o, _ := json.Marshal(parsed.BuildOptions)
|
||||
newParsedBuildOptions := &core.BuildOptions{}
|
||||
e := json.Unmarshal(o, newParsedBuildOptions)
|
||||
assert.NilError(t, e)
|
||||
if getTsBaseline != nil {
|
||||
assert.DeepEqual(t, tsBaseline.options, newParsedBuildOptions, cmpopts.IgnoreUnexported(core.BuildOptions{}))
|
||||
}
|
||||
|
||||
compilerOpts, _ := json.Marshal(parsed.CompilerOptions)
|
||||
newParsedCompilerOptions := &core.CompilerOptions{}
|
||||
e = json.Unmarshal(compilerOpts, newParsedCompilerOptions)
|
||||
assert.NilError(t, e)
|
||||
if getTsBaseline != nil {
|
||||
assert.DeepEqual(t, tsBaseline.compilerOptions, newParsedCompilerOptions, cmpopts.IgnoreUnexported(core.CompilerOptions{}))
|
||||
}
|
||||
|
||||
newParsedWatchOptions := core.WatchOptions{}
|
||||
e = json.Unmarshal(o, &newParsedWatchOptions)
|
||||
assert.NilError(t, e)
|
||||
|
||||
// !!! useful for debugging but will not pass due to `none` as enum options
|
||||
// assert.DeepEqual(t, tsBaseline.watchoptions, newParsedWatchOptions)
|
||||
|
||||
var formattedErrors strings.Builder
|
||||
diagnosticwriter.WriteFormatDiagnostics(&formattedErrors, diagnosticwriter.FromASTDiagnostics(parsed.Errors), &diagnosticwriter.FormattingOptions{NewLine: "\n"})
|
||||
newBaselineErrors := formattedErrors.String()
|
||||
|
||||
// !!!
|
||||
// useful for debugging--compares the new errors with the old errors. currently will NOT pass because of unimplemented options, not completely identical enum options, etc
|
||||
// assert.Equal(t, tsBaseline.errors, newBaselineErrors)
|
||||
|
||||
baseline.Run(t, f.testName+".js", formatNewBaselineBuild(f.commandLine, o, compilerOpts, newBaselineProjects, newBaselineErrors), baseline.Options{Subfolder: "tsoptions/commandLineParsing"})
|
||||
})
|
||||
}
|
||||
|
||||
func parseExistingCompilerBaselineBuild(t *testing.T, baseline string) *TestCommandLineParserBuild {
|
||||
_, rest, _ := strings.Cut(baseline, "buildOptions::\n")
|
||||
buildOptions, rest, watchFound := strings.Cut(rest, "\nWatchOptions::\n")
|
||||
watchOptions, rest, _ := strings.Cut(rest, "\nProjects::\n")
|
||||
projects, errors, _ := strings.Cut(rest, "\nErrors::\n")
|
||||
|
||||
baselineBuildOptions := &core.BuildOptions{}
|
||||
e := json.Unmarshal([]byte(buildOptions), &baselineBuildOptions)
|
||||
assert.NilError(t, e)
|
||||
|
||||
baselineCompilerOptions := &core.CompilerOptions{}
|
||||
e = json.Unmarshal([]byte(buildOptions), &baselineCompilerOptions)
|
||||
assert.NilError(t, e)
|
||||
|
||||
baselineWatchOptions := &core.WatchOptions{}
|
||||
if watchFound && watchOptions != "" {
|
||||
e2 := json.Unmarshal([]byte(watchOptions), &baselineWatchOptions)
|
||||
assert.NilError(t, e2)
|
||||
}
|
||||
|
||||
return &TestCommandLineParserBuild{
|
||||
options: baselineBuildOptions,
|
||||
compilerOptions: baselineCompilerOptions,
|
||||
watchoptions: baselineWatchOptions,
|
||||
projects: projects,
|
||||
errors: errors,
|
||||
}
|
||||
}
|
||||
|
||||
func formatNewBaselineBuild(
|
||||
commandLine []string,
|
||||
opts []byte,
|
||||
compilerOpts []byte,
|
||||
projects string,
|
||||
errors string,
|
||||
) string {
|
||||
var formatted strings.Builder
|
||||
formatted.WriteString("Args::\n")
|
||||
formatted.WriteByte('[')
|
||||
for i, arg := range commandLine {
|
||||
if i > 0 {
|
||||
formatted.WriteString(", ")
|
||||
}
|
||||
formatted.WriteByte('"')
|
||||
formatted.WriteString(arg)
|
||||
formatted.WriteByte('"')
|
||||
}
|
||||
formatted.WriteByte(']')
|
||||
formatted.WriteString("\n\nbuildOptions::\n")
|
||||
formatted.Write(opts)
|
||||
formatted.WriteString("\n\ncompilerOptions::\n")
|
||||
formatted.Write(compilerOpts)
|
||||
// todo: watch options not implemented
|
||||
// formatted.WriteString("WatchOptions::\n")
|
||||
formatted.WriteString("\n\nProjects::\n")
|
||||
formatted.WriteString(projects)
|
||||
formatted.WriteString("\n\nErrors::\n")
|
||||
formatted.WriteString(errors)
|
||||
return formatted.String()
|
||||
}
|
||||
|
||||
func createSubScenario(scenarioKind string, subScenarioName string, commandline []string, opts ...[]*tsoptions.CommandLineOption) *commandLineSubScenario {
|
||||
subScenarioName = scenarioKind + "/" + subScenarioName
|
||||
baselineFileName := "tests/baselines/reference/config/commandLineParsing/" + subScenarioName + ".js"
|
||||
|
||||
result := &commandLineSubScenario{
|
||||
filefixture.FromFile(subScenarioName, filepath.Join(repo.TypeScriptSubmodulePath(), baselineFileName)),
|
||||
subScenarioName,
|
||||
commandline,
|
||||
nil,
|
||||
}
|
||||
if len(opts) > 0 {
|
||||
result.optDecls = opts[0]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
type subScenarioInput struct {
|
||||
name string
|
||||
commandLineArgs []string
|
||||
}
|
||||
|
||||
func (f subScenarioInput) createSubScenario(scenarioKind string) *commandLineSubScenario {
|
||||
return createSubScenario(scenarioKind, f.name, f.commandLineArgs)
|
||||
}
|
||||
|
||||
type commandLineSubScenario struct {
|
||||
baseline filefixture.Fixture
|
||||
testName string
|
||||
commandLine []string
|
||||
optDecls []*tsoptions.CommandLineOption
|
||||
}
|
||||
|
||||
type verifyNull struct {
|
||||
subScenario string
|
||||
optionName string
|
||||
nonNullValue string
|
||||
optDecls []*tsoptions.CommandLineOption
|
||||
}
|
||||
|
||||
type TestCommandLineParser struct {
|
||||
options *core.CompilerOptions
|
||||
watchoptions *core.WatchOptions
|
||||
fileNames, errors string
|
||||
}
|
||||
|
||||
type TestCommandLineParserBuild struct {
|
||||
options *core.BuildOptions
|
||||
compilerOptions *core.CompilerOptions
|
||||
watchoptions *core.WatchOptions
|
||||
projects, errors string
|
||||
}
|
||||
|
||||
func TestParseBuildCommandLine(t *testing.T) {
|
||||
t.Parallel()
|
||||
repo.SkipIfNoTypeScriptSubmodule(t)
|
||||
|
||||
parseCommandLineSubScenarios := []*subScenarioInput{
|
||||
{"parse build without any options ", []string{}},
|
||||
{"Parse multiple options", []string{"--verbose", "--force", "tests"}},
|
||||
{"Parse option with invalid option", []string{"--verbose", "--invalidOption"}},
|
||||
{"Parse multiple flags with input projects at the end", []string{"--force", "--verbose", "src", "tests"}},
|
||||
{"Parse multiple flags with input projects in the middle", []string{"--force", "src", "tests", "--verbose"}},
|
||||
{"Parse multiple flags with input projects in the beginning", []string{"src", "tests", "--force", "--verbose"}},
|
||||
{"parse build with --incremental", []string{"--incremental", "tests"}},
|
||||
{"parse build with --locale en-us", []string{"--locale", "en-us", "src"}},
|
||||
{"parse build with --tsBuildInfoFile", []string{"--tsBuildInfoFile", "build.tsbuildinfo", "tests"}},
|
||||
{"reports other common may not be used with --build flags", []string{"--strict"}},
|
||||
{`--clean and --force together is invalid`, []string{"--clean", "--force"}},
|
||||
{`--clean and --verbose together is invalid`, []string{"--clean", "--verbose"}},
|
||||
{`--clean and --watch together is invalid`, []string{"--clean", "--watch"}},
|
||||
{`--watch and --dry together is invalid`, []string{"--watch", "--dry"}},
|
||||
{"parse --watchFile", []string{"--watchFile", "UseFsEvents", "--verbose"}},
|
||||
{"parse --watchDirectory", []string{"--watchDirectory", "FixedPollingInterval", "--verbose"}},
|
||||
{"parse --fallbackPolling", []string{"--fallbackPolling", "PriorityInterval", "--verbose"}},
|
||||
{"parse --synchronousWatchDirectory", []string{"--synchronousWatchDirectory", "--verbose"}},
|
||||
{"errors on missing argument", []string{"--verbose", "--fallbackPolling"}},
|
||||
{"errors on invalid excludeDirectories", []string{"--excludeDirectories", "**/../*"}},
|
||||
{"parse --excludeFiles", []string{"--excludeFiles", "**/temp/*.ts"}},
|
||||
{"errors on invalid excludeFiles", []string{"--excludeFiles", "**/../*"}},
|
||||
}
|
||||
|
||||
for _, testCase := range parseCommandLineSubScenarios {
|
||||
testCase.createSubScenario("parseBuildOptions").assertBuildParseResult(t)
|
||||
}
|
||||
|
||||
extraScenarios := []*subScenarioInput{
|
||||
{`parse --builders`, []string{"--builders", "2"}},
|
||||
{`--singleThreaded and --builders together`, []string{"--singleThreaded", "--builders", "2"}},
|
||||
{`reports error when --builders is 0`, []string{"--builders", "0"}},
|
||||
{`reports error when --builders is negative`, []string{"--builders", "-1"}},
|
||||
{`reports error when --builders is invalid type`, []string{"--builders", "invalid"}},
|
||||
}
|
||||
|
||||
for _, testCase := range extraScenarios {
|
||||
testCase.createSubScenario("parseBuildOptions").assertBuildParseResultWithTsBaseline(t, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAffectsBuildInfo(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("should have affectsBuildInfo true for every option with affectsSemanticDiagnostics", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
for _, option := range tsoptions.OptionsDeclarations {
|
||||
if option.AffectsSemanticDiagnostics {
|
||||
// semantic diagnostics affect the build info, so ensure they're included
|
||||
assert.Assert(t, option.AffectsBuildInfo)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
79
tools/tsgo/internal/tsoptions/decls_test.go
Normal file
79
tools/tsgo/internal/tsoptions/decls_test.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package tsoptions_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/tsoptions"
|
||||
)
|
||||
|
||||
func TestCompilerOptionsDeclaration(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
decls := make(map[string]*tsoptions.CommandLineOption)
|
||||
|
||||
for _, decl := range tsoptions.OptionsDeclarations {
|
||||
decls[strings.ToLower(decl.Name)] = decl
|
||||
}
|
||||
|
||||
internalOptions := []string{
|
||||
"allowNonTsExtensions",
|
||||
"build",
|
||||
"configFilePath",
|
||||
"noDtsResolution",
|
||||
"noEmitForJsFiles",
|
||||
"pathsBasePath",
|
||||
"suppressOutputPathCheck",
|
||||
"build",
|
||||
}
|
||||
|
||||
internalOptionsMap := make(map[string]string)
|
||||
for _, opt := range internalOptions {
|
||||
internalOptionsMap[strings.ToLower(opt)] = opt
|
||||
}
|
||||
|
||||
compilerOptionsType := reflect.TypeFor[core.CompilerOptions]()
|
||||
for field := range compilerOptionsType.Fields() {
|
||||
if !field.IsExported() {
|
||||
continue
|
||||
}
|
||||
|
||||
lowerName := strings.ToLower(field.Name)
|
||||
|
||||
decl := decls[lowerName]
|
||||
if decl == nil {
|
||||
if name, ok := internalOptionsMap[lowerName]; ok {
|
||||
checkCompilerOptionJsonTagName(t, field, name)
|
||||
continue
|
||||
}
|
||||
t.Errorf("CompilerOptions.%s has no options declaration", field.Name)
|
||||
continue
|
||||
}
|
||||
delete(decls, lowerName)
|
||||
|
||||
checkCompilerOptionJsonTagName(t, field, decl.Name)
|
||||
}
|
||||
|
||||
skippedOptions := []string{
|
||||
"plugins",
|
||||
}
|
||||
|
||||
for _, opt := range skippedOptions {
|
||||
delete(decls, strings.ToLower(opt))
|
||||
}
|
||||
|
||||
for _, decl := range decls {
|
||||
t.Errorf("Option declaration %s is not present in CompilerOptions", decl.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func checkCompilerOptionJsonTagName(t *testing.T, field reflect.StructField, name string) {
|
||||
t.Helper()
|
||||
want := name + ",omitzero"
|
||||
got := field.Tag.Get("json")
|
||||
if got != want {
|
||||
t.Errorf("Field %s has json tag %s, but the option declaration has name %s", field.Name, got, want)
|
||||
}
|
||||
}
|
||||
69
tools/tsgo/internal/tsoptions/declsbuild.go
Normal file
69
tools/tsgo/internal/tsoptions/declsbuild.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package tsoptions
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/diagnostics"
|
||||
)
|
||||
|
||||
var TscBuildOption = CommandLineOption{
|
||||
Name: "build",
|
||||
Kind: "boolean",
|
||||
ShortName: "b",
|
||||
ShowInSimplifiedHelpView: true,
|
||||
Category: diagnostics.Command_line_Options,
|
||||
Description: diagnostics.Build_one_or_more_projects_and_their_dependencies_if_out_of_date,
|
||||
DefaultValueDescription: false,
|
||||
}
|
||||
|
||||
var OptionsForBuild = []*CommandLineOption{
|
||||
&TscBuildOption,
|
||||
{
|
||||
Name: "verbose",
|
||||
ShortName: "v",
|
||||
Category: diagnostics.Command_line_Options,
|
||||
Description: diagnostics.Enable_verbose_logging,
|
||||
Kind: "boolean",
|
||||
DefaultValueDescription: false,
|
||||
},
|
||||
{
|
||||
Name: "dry",
|
||||
ShortName: "d",
|
||||
Category: diagnostics.Command_line_Options,
|
||||
Description: diagnostics.Show_what_would_be_built_or_deleted_if_specified_with_clean,
|
||||
Kind: "boolean",
|
||||
DefaultValueDescription: false,
|
||||
},
|
||||
{
|
||||
Name: "force",
|
||||
ShortName: "f",
|
||||
Category: diagnostics.Command_line_Options,
|
||||
Description: diagnostics.Build_all_projects_including_those_that_appear_to_be_up_to_date,
|
||||
Kind: "boolean",
|
||||
DefaultValueDescription: false,
|
||||
},
|
||||
{
|
||||
Name: "clean",
|
||||
Category: diagnostics.Command_line_Options,
|
||||
Description: diagnostics.Delete_the_outputs_of_all_projects,
|
||||
Kind: "boolean",
|
||||
DefaultValueDescription: false,
|
||||
},
|
||||
{
|
||||
Name: "builders",
|
||||
Kind: CommandLineOptionTypeNumber,
|
||||
Category: diagnostics.Command_line_Options,
|
||||
Description: diagnostics.Set_the_number_of_projects_to_build_concurrently,
|
||||
DefaultValueDescription: diagnostics.X_4_unless_singleThreaded_is_passed,
|
||||
minValue: 1,
|
||||
},
|
||||
{
|
||||
Name: "stopBuildOnErrors",
|
||||
Category: diagnostics.Command_line_Options,
|
||||
Description: diagnostics.Skip_building_downstream_projects_on_error_in_upstream_project,
|
||||
Kind: "boolean",
|
||||
DefaultValueDescription: false,
|
||||
},
|
||||
}
|
||||
|
||||
var BuildOpts = slices.Concat(commonOptionsWithBuild, OptionsForBuild)
|
||||
1264
tools/tsgo/internal/tsoptions/declscompiler.go
Normal file
1264
tools/tsgo/internal/tsoptions/declscompiler.go
Normal file
File diff suppressed because it is too large
Load Diff
29
tools/tsgo/internal/tsoptions/declstypeacquisition.go
Normal file
29
tools/tsgo/internal/tsoptions/declstypeacquisition.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package tsoptions
|
||||
|
||||
var typeAcquisitionDeclaration = &CommandLineOption{
|
||||
Name: "typeAcquisition",
|
||||
Kind: CommandLineOptionTypeObject,
|
||||
ElementOptions: commandLineOptionsToMap(typeAcquisitionDecls),
|
||||
}
|
||||
|
||||
// Do not delete this without updating the website's tsconfig generation.
|
||||
var typeAcquisitionDecls = []*CommandLineOption{
|
||||
{
|
||||
Name: "enable",
|
||||
Kind: CommandLineOptionTypeBoolean,
|
||||
DefaultValueDescription: false,
|
||||
},
|
||||
{
|
||||
Name: "include",
|
||||
Kind: CommandLineOptionTypeList,
|
||||
},
|
||||
{
|
||||
Name: "exclude",
|
||||
Kind: CommandLineOptionTypeList,
|
||||
},
|
||||
{
|
||||
Name: "disableFilenameBasedTypeAcquisition",
|
||||
Kind: CommandLineOptionTypeBoolean,
|
||||
DefaultValueDescription: false,
|
||||
},
|
||||
}
|
||||
88
tools/tsgo/internal/tsoptions/declswatch.go
Normal file
88
tools/tsgo/internal/tsoptions/declswatch.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package tsoptions
|
||||
|
||||
import (
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/diagnostics"
|
||||
)
|
||||
|
||||
var OptionsForWatch = []*CommandLineOption{
|
||||
{
|
||||
Name: "watchInterval",
|
||||
Kind: CommandLineOptionTypeNumber,
|
||||
Category: diagnostics.Watch_and_Build_Modes,
|
||||
},
|
||||
{
|
||||
Name: "watchFile",
|
||||
Kind: CommandLineOptionTypeEnum,
|
||||
// new Map(Object.entries({
|
||||
// fixedpollinginterval: WatchFileKind.FixedPollingInterval,
|
||||
// prioritypollinginterval: WatchFileKind.PriorityPollingInterval,
|
||||
// dynamicprioritypolling: WatchFileKind.DynamicPriorityPolling,
|
||||
// fixedchunksizepolling: WatchFileKind.FixedChunkSizePolling,
|
||||
// usefsevents: WatchFileKind.UseFsEvents,
|
||||
// usefseventsonparentdirectory: WatchFileKind.UseFsEventsOnParentDirectory,
|
||||
// })),
|
||||
Category: diagnostics.Watch_and_Build_Modes,
|
||||
Description: diagnostics.Specify_how_the_TypeScript_watch_mode_works,
|
||||
DefaultValueDescription: core.WatchFileKindUseFsEvents,
|
||||
},
|
||||
{
|
||||
Name: "watchDirectory",
|
||||
Kind: CommandLineOptionTypeEnum,
|
||||
// new Map(Object.entries({
|
||||
// usefsevents: WatchDirectoryKind.UseFsEvents,
|
||||
// fixedpollinginterval: WatchDirectoryKind.FixedPollingInterval,
|
||||
// dynamicprioritypolling: WatchDirectoryKind.DynamicPriorityPolling,
|
||||
// fixedchunksizepolling: WatchDirectoryKind.FixedChunkSizePolling,
|
||||
// })),
|
||||
Category: diagnostics.Watch_and_Build_Modes,
|
||||
Description: diagnostics.Specify_how_directories_are_watched_on_systems_that_lack_recursive_file_watching_functionality,
|
||||
DefaultValueDescription: core.WatchDirectoryKindUseFsEvents,
|
||||
},
|
||||
{
|
||||
Name: "fallbackPolling",
|
||||
Kind: CommandLineOptionTypeEnum,
|
||||
// new Map(Object.entries({
|
||||
// fixedinterval: PollingWatchKind.FixedInterval,
|
||||
// priorityinterval: PollingWatchKind.PriorityInterval,
|
||||
// dynamicpriority: PollingWatchKind.DynamicPriority,
|
||||
// fixedchunksize: PollingWatchKind.FixedChunkSize,
|
||||
// })),
|
||||
Category: diagnostics.Watch_and_Build_Modes,
|
||||
Description: diagnostics.Specify_what_approach_the_watcher_should_use_if_the_system_runs_out_of_native_file_watchers,
|
||||
DefaultValueDescription: core.PollingKindPriorityInterval,
|
||||
},
|
||||
{
|
||||
Name: "synchronousWatchDirectory",
|
||||
Kind: CommandLineOptionTypeBoolean,
|
||||
Category: diagnostics.Watch_and_Build_Modes,
|
||||
Description: diagnostics.Synchronously_call_callbacks_and_update_the_state_of_directory_watchers_on_platforms_that_don_t_support_recursive_watching_natively,
|
||||
DefaultValueDescription: false,
|
||||
},
|
||||
{
|
||||
Name: "excludeDirectories",
|
||||
Kind: CommandLineOptionTypeList,
|
||||
// element: {
|
||||
// Name: "excludeDirectory",
|
||||
// Kind: "string",
|
||||
// isFilePath: true,
|
||||
// extraValidation: specToDiagnostic,
|
||||
// },
|
||||
allowConfigDirTemplateSubstitution: true,
|
||||
Category: diagnostics.Watch_and_Build_Modes,
|
||||
Description: diagnostics.Remove_a_list_of_directories_from_the_watch_process,
|
||||
},
|
||||
{
|
||||
Name: "excludeFiles",
|
||||
Kind: CommandLineOptionTypeList,
|
||||
// element: {
|
||||
// Name: "excludeFile",
|
||||
// Kind: "string",
|
||||
// isFilePath: true,
|
||||
// extraValidation: specToDiagnostic,
|
||||
// },
|
||||
allowConfigDirTemplateSubstitution: true,
|
||||
Category: diagnostics.Watch_and_Build_Modes,
|
||||
Description: diagnostics.Remove_a_list_of_files_from_the_watch_mode_s_processing,
|
||||
},
|
||||
}
|
||||
67
tools/tsgo/internal/tsoptions/diagnostics.go
Normal file
67
tools/tsgo/internal/tsoptions/diagnostics.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package tsoptions
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/diagnostics"
|
||||
)
|
||||
|
||||
type DidYouMeanOptionsDiagnostics struct {
|
||||
alternateMode *AlternateModeDiagnostics
|
||||
OptionDeclarations []*CommandLineOption
|
||||
UnknownOptionDiagnostic *diagnostics.Message
|
||||
UnknownDidYouMeanDiagnostic *diagnostics.Message
|
||||
}
|
||||
|
||||
type AlternateModeDiagnostics struct {
|
||||
diagnostic *diagnostics.Message
|
||||
optionsNameMap *NameMap
|
||||
}
|
||||
|
||||
type ParseCommandLineWorkerDiagnostics struct {
|
||||
didYouMean DidYouMeanOptionsDiagnostics
|
||||
optionsNameMap *NameMap
|
||||
optionsNameMapOnce sync.Once
|
||||
OptionTypeMismatchDiagnostic *diagnostics.Message
|
||||
}
|
||||
|
||||
var CompilerOptionsDidYouMeanDiagnostics = getParseCommandLineWorkerDiagnostics(OptionsDeclarations)
|
||||
|
||||
func getParseCommandLineWorkerDiagnostics(decls []*CommandLineOption) *ParseCommandLineWorkerDiagnostics {
|
||||
// this will only return the correct diagnostics for `compiler` mode, and is factored into a function for testing reasons.
|
||||
return &ParseCommandLineWorkerDiagnostics{
|
||||
didYouMean: DidYouMeanOptionsDiagnostics{
|
||||
alternateMode: &AlternateModeDiagnostics{
|
||||
diagnostic: diagnostics.Compiler_option_0_may_only_be_used_with_build,
|
||||
optionsNameMap: BuildNameMap,
|
||||
},
|
||||
OptionDeclarations: decls,
|
||||
UnknownOptionDiagnostic: diagnostics.Unknown_compiler_option_0,
|
||||
UnknownDidYouMeanDiagnostic: diagnostics.Unknown_compiler_option_0_Did_you_mean_1,
|
||||
},
|
||||
OptionTypeMismatchDiagnostic: diagnostics.Compiler_option_0_expects_an_argument,
|
||||
}
|
||||
}
|
||||
|
||||
var watchOptionsDidYouMeanDiagnostics = &ParseCommandLineWorkerDiagnostics{
|
||||
didYouMean: DidYouMeanOptionsDiagnostics{
|
||||
// no alternateMode
|
||||
OptionDeclarations: OptionsForWatch,
|
||||
UnknownOptionDiagnostic: diagnostics.Unknown_watch_option_0,
|
||||
UnknownDidYouMeanDiagnostic: diagnostics.Unknown_watch_option_0_Did_you_mean_1,
|
||||
},
|
||||
OptionTypeMismatchDiagnostic: diagnostics.Watch_option_0_requires_a_value_of_type_1,
|
||||
}
|
||||
|
||||
var buildOptionsDidYouMeanDiagnostics = &ParseCommandLineWorkerDiagnostics{
|
||||
didYouMean: DidYouMeanOptionsDiagnostics{
|
||||
alternateMode: &AlternateModeDiagnostics{
|
||||
diagnostic: diagnostics.Compiler_option_0_may_not_be_used_with_build,
|
||||
optionsNameMap: CompilerNameMap,
|
||||
},
|
||||
OptionDeclarations: BuildOpts,
|
||||
UnknownOptionDiagnostic: diagnostics.Unknown_build_option_0,
|
||||
UnknownDidYouMeanDiagnostic: diagnostics.Unknown_build_option_0_Did_you_mean_1,
|
||||
},
|
||||
OptionTypeMismatchDiagnostic: diagnostics.Build_option_0_requires_a_value_of_type_1,
|
||||
}
|
||||
255
tools/tsgo/internal/tsoptions/enummaps.go
Normal file
255
tools/tsgo/internal/tsoptions/enummaps.go
Normal file
@@ -0,0 +1,255 @@
|
||||
package tsoptions
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/collections"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
)
|
||||
|
||||
var LibMap = collections.NewOrderedMapFromList([]collections.MapEntry[string, any]{
|
||||
// JavaScript only
|
||||
{Key: "es5", Value: "lib.es5.d.ts"},
|
||||
{Key: "es6", Value: "lib.es2015.d.ts"},
|
||||
{Key: "es2015", Value: "lib.es2015.d.ts"},
|
||||
{Key: "es7", Value: "lib.es2016.d.ts"},
|
||||
{Key: "es2016", Value: "lib.es2016.d.ts"},
|
||||
{Key: "es2017", Value: "lib.es2017.d.ts"},
|
||||
{Key: "es2018", Value: "lib.es2018.d.ts"},
|
||||
{Key: "es2019", Value: "lib.es2019.d.ts"},
|
||||
{Key: "es2020", Value: "lib.es2020.d.ts"},
|
||||
{Key: "es2021", Value: "lib.es2021.d.ts"},
|
||||
{Key: "es2022", Value: "lib.es2022.d.ts"},
|
||||
{Key: "es2023", Value: "lib.es2023.d.ts"},
|
||||
{Key: "es2024", Value: "lib.es2024.d.ts"},
|
||||
{Key: "es2025", Value: "lib.es2025.d.ts"},
|
||||
{Key: "esnext", Value: "lib.esnext.d.ts"},
|
||||
// Host only
|
||||
{Key: "dom", Value: "lib.dom.d.ts"},
|
||||
{Key: "dom.iterable", Value: "lib.dom.iterable.d.ts"},
|
||||
{Key: "dom.asynciterable", Value: "lib.dom.asynciterable.d.ts"},
|
||||
{Key: "webworker", Value: "lib.webworker.d.ts"},
|
||||
{Key: "webworker.importscripts", Value: "lib.webworker.importscripts.d.ts"},
|
||||
{Key: "webworker.iterable", Value: "lib.webworker.iterable.d.ts"},
|
||||
{Key: "webworker.asynciterable", Value: "lib.webworker.asynciterable.d.ts"},
|
||||
{Key: "scripthost", Value: "lib.scripthost.d.ts"},
|
||||
// ES2015 and later By-feature options
|
||||
{Key: "es2015.core", Value: "lib.es2015.core.d.ts"},
|
||||
{Key: "es2015.collection", Value: "lib.es2015.collection.d.ts"},
|
||||
{Key: "es2015.generator", Value: "lib.es2015.generator.d.ts"},
|
||||
{Key: "es2015.iterable", Value: "lib.es2015.iterable.d.ts"},
|
||||
{Key: "es2015.promise", Value: "lib.es2015.promise.d.ts"},
|
||||
{Key: "es2015.proxy", Value: "lib.es2015.proxy.d.ts"},
|
||||
{Key: "es2015.reflect", Value: "lib.es2015.reflect.d.ts"},
|
||||
{Key: "es2015.symbol", Value: "lib.es2015.symbol.d.ts"},
|
||||
{Key: "es2015.symbol.wellknown", Value: "lib.es2015.symbol.wellknown.d.ts"},
|
||||
{Key: "es2016.array.include", Value: "lib.es2016.array.include.d.ts"},
|
||||
{Key: "es2016.intl", Value: "lib.es2016.intl.d.ts"},
|
||||
{Key: "es2017.arraybuffer", Value: "lib.es2017.arraybuffer.d.ts"},
|
||||
{Key: "es2017.date", Value: "lib.es2017.date.d.ts"},
|
||||
{Key: "es2017.object", Value: "lib.es2017.object.d.ts"},
|
||||
{Key: "es2017.sharedmemory", Value: "lib.es2017.sharedmemory.d.ts"},
|
||||
{Key: "es2017.string", Value: "lib.es2017.string.d.ts"},
|
||||
{Key: "es2017.intl", Value: "lib.es2017.intl.d.ts"},
|
||||
{Key: "es2017.typedarrays", Value: "lib.es2017.typedarrays.d.ts"},
|
||||
{Key: "es2018.asyncgenerator", Value: "lib.es2018.asyncgenerator.d.ts"},
|
||||
{Key: "es2018.asynciterable", Value: "lib.es2018.asynciterable.d.ts"},
|
||||
{Key: "es2018.intl", Value: "lib.es2018.intl.d.ts"},
|
||||
{Key: "es2018.promise", Value: "lib.es2018.promise.d.ts"},
|
||||
{Key: "es2018.regexp", Value: "lib.es2018.regexp.d.ts"},
|
||||
{Key: "es2019.array", Value: "lib.es2019.array.d.ts"},
|
||||
{Key: "es2019.object", Value: "lib.es2019.object.d.ts"},
|
||||
{Key: "es2019.string", Value: "lib.es2019.string.d.ts"},
|
||||
{Key: "es2019.symbol", Value: "lib.es2019.symbol.d.ts"},
|
||||
{Key: "es2019.intl", Value: "lib.es2019.intl.d.ts"},
|
||||
{Key: "es2020.bigint", Value: "lib.es2020.bigint.d.ts"},
|
||||
{Key: "es2020.date", Value: "lib.es2020.date.d.ts"},
|
||||
{Key: "es2020.promise", Value: "lib.es2020.promise.d.ts"},
|
||||
{Key: "es2020.sharedmemory", Value: "lib.es2020.sharedmemory.d.ts"},
|
||||
{Key: "es2020.string", Value: "lib.es2020.string.d.ts"},
|
||||
{Key: "es2020.symbol.wellknown", Value: "lib.es2020.symbol.wellknown.d.ts"},
|
||||
{Key: "es2020.intl", Value: "lib.es2020.intl.d.ts"},
|
||||
{Key: "es2020.number", Value: "lib.es2020.number.d.ts"},
|
||||
{Key: "es2021.promise", Value: "lib.es2021.promise.d.ts"},
|
||||
{Key: "es2021.string", Value: "lib.es2021.string.d.ts"},
|
||||
{Key: "es2021.weakref", Value: "lib.es2021.weakref.d.ts"},
|
||||
{Key: "es2021.intl", Value: "lib.es2021.intl.d.ts"},
|
||||
{Key: "es2022.array", Value: "lib.es2022.array.d.ts"},
|
||||
{Key: "es2022.error", Value: "lib.es2022.error.d.ts"},
|
||||
{Key: "es2022.intl", Value: "lib.es2022.intl.d.ts"},
|
||||
{Key: "es2022.object", Value: "lib.es2022.object.d.ts"},
|
||||
{Key: "es2022.string", Value: "lib.es2022.string.d.ts"},
|
||||
{Key: "es2022.regexp", Value: "lib.es2022.regexp.d.ts"},
|
||||
{Key: "es2023.array", Value: "lib.es2023.array.d.ts"},
|
||||
{Key: "es2023.collection", Value: "lib.es2023.collection.d.ts"},
|
||||
{Key: "es2023.intl", Value: "lib.es2023.intl.d.ts"},
|
||||
{Key: "es2024.arraybuffer", Value: "lib.es2024.arraybuffer.d.ts"},
|
||||
{Key: "es2024.collection", Value: "lib.es2024.collection.d.ts"},
|
||||
{Key: "es2024.object", Value: "lib.es2024.object.d.ts"},
|
||||
{Key: "es2024.promise", Value: "lib.es2024.promise.d.ts"},
|
||||
{Key: "es2024.regexp", Value: "lib.es2024.regexp.d.ts"},
|
||||
{Key: "es2024.sharedmemory", Value: "lib.es2024.sharedmemory.d.ts"},
|
||||
{Key: "es2024.string", Value: "lib.es2024.string.d.ts"},
|
||||
{Key: "es2025.collection", Value: "lib.es2025.collection.d.ts"},
|
||||
{Key: "es2025.float16", Value: "lib.es2025.float16.d.ts"},
|
||||
{Key: "es2025.intl", Value: "lib.es2025.intl.d.ts"},
|
||||
{Key: "es2025.iterator", Value: "lib.es2025.iterator.d.ts"},
|
||||
{Key: "es2025.promise", Value: "lib.es2025.promise.d.ts"},
|
||||
{Key: "es2025.regexp", Value: "lib.es2025.regexp.d.ts"},
|
||||
// Fallback for backward compatibility
|
||||
{Key: "esnext.asynciterable", Value: "lib.es2018.asynciterable.d.ts"},
|
||||
{Key: "esnext.symbol", Value: "lib.es2019.symbol.d.ts"},
|
||||
{Key: "esnext.bigint", Value: "lib.es2020.bigint.d.ts"},
|
||||
{Key: "esnext.weakref", Value: "lib.es2021.weakref.d.ts"},
|
||||
{Key: "esnext.object", Value: "lib.es2024.object.d.ts"},
|
||||
{Key: "esnext.regexp", Value: "lib.es2024.regexp.d.ts"},
|
||||
{Key: "esnext.string", Value: "lib.es2024.string.d.ts"},
|
||||
{Key: "esnext.float16", Value: "lib.es2025.float16.d.ts"},
|
||||
{Key: "esnext.iterator", Value: "lib.es2025.iterator.d.ts"},
|
||||
{Key: "esnext.promise", Value: "lib.es2025.promise.d.ts"},
|
||||
// ESNext By-feature options
|
||||
{Key: "esnext.array", Value: "lib.esnext.array.d.ts"},
|
||||
{Key: "esnext.collection", Value: "lib.esnext.collection.d.ts"},
|
||||
{Key: "esnext.date", Value: "lib.esnext.date.d.ts"},
|
||||
{Key: "esnext.decorators", Value: "lib.esnext.decorators.d.ts"},
|
||||
{Key: "esnext.disposable", Value: "lib.esnext.disposable.d.ts"},
|
||||
{Key: "esnext.error", Value: "lib.esnext.error.d.ts"},
|
||||
{Key: "esnext.intl", Value: "lib.esnext.intl.d.ts"},
|
||||
{Key: "esnext.sharedmemory", Value: "lib.esnext.sharedmemory.d.ts"},
|
||||
{Key: "esnext.temporal", Value: "lib.esnext.temporal.d.ts"},
|
||||
{Key: "esnext.typedarrays", Value: "lib.esnext.typedarrays.d.ts"},
|
||||
// Decorators
|
||||
{Key: "decorators", Value: "lib.decorators.d.ts"},
|
||||
{Key: "decorators.legacy", Value: "lib.decorators.legacy.d.ts"},
|
||||
})
|
||||
|
||||
var (
|
||||
Libs = slices.Collect(LibMap.Keys())
|
||||
LibFilesSet = collections.NewSetFromItems(core.Map(slices.Collect(LibMap.Values()), func(s any) string { return s.(string) })...)
|
||||
)
|
||||
|
||||
func GetLibFileName(libName string) (string, bool) {
|
||||
// checks if the libName is a valid lib name or file name and converts the lib name to the filename if needed
|
||||
libName = tspath.ToFileNameLowerCase(libName)
|
||||
if LibFilesSet.Has(libName) {
|
||||
return libName, true
|
||||
}
|
||||
lib, ok := LibMap.Get(libName)
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
return lib.(string), true
|
||||
}
|
||||
|
||||
var moduleResolutionOptionMap = collections.NewOrderedMapFromList([]collections.MapEntry[string, any]{
|
||||
{Key: "node16", Value: core.ModuleResolutionKindNode16},
|
||||
{Key: "nodenext", Value: core.ModuleResolutionKindNodeNext},
|
||||
{Key: "bundler", Value: core.ModuleResolutionKindBundler},
|
||||
{Key: "classic", Value: core.ModuleResolutionKindClassic},
|
||||
{Key: "node", Value: core.ModuleResolutionKindNode10},
|
||||
{Key: "node10", Value: core.ModuleResolutionKindNode10},
|
||||
})
|
||||
|
||||
var targetOptionMap = collections.NewOrderedMapFromList([]collections.MapEntry[string, any]{
|
||||
{Key: "es5", Value: core.ScriptTargetES5},
|
||||
{Key: "es6", Value: core.ScriptTargetES2015},
|
||||
{Key: "es2015", Value: core.ScriptTargetES2015},
|
||||
{Key: "es2016", Value: core.ScriptTargetES2016},
|
||||
{Key: "es2017", Value: core.ScriptTargetES2017},
|
||||
{Key: "es2018", Value: core.ScriptTargetES2018},
|
||||
{Key: "es2019", Value: core.ScriptTargetES2019},
|
||||
{Key: "es2020", Value: core.ScriptTargetES2020},
|
||||
{Key: "es2021", Value: core.ScriptTargetES2021},
|
||||
{Key: "es2022", Value: core.ScriptTargetES2022},
|
||||
{Key: "es2023", Value: core.ScriptTargetES2023},
|
||||
{Key: "es2024", Value: core.ScriptTargetES2024},
|
||||
{Key: "es2025", Value: core.ScriptTargetES2025},
|
||||
{Key: "esnext", Value: core.ScriptTargetESNext},
|
||||
})
|
||||
|
||||
var moduleOptionMap = collections.NewOrderedMapFromList([]collections.MapEntry[string, any]{
|
||||
{Key: "commonjs", Value: core.ModuleKindCommonJS},
|
||||
{Key: "amd", Value: core.ModuleKindAMD},
|
||||
{Key: "system", Value: core.ModuleKindSystem},
|
||||
{Key: "umd", Value: core.ModuleKindUMD},
|
||||
{Key: "es6", Value: core.ModuleKindES2015},
|
||||
{Key: "es2015", Value: core.ModuleKindES2015},
|
||||
{Key: "es2020", Value: core.ModuleKindES2020},
|
||||
{Key: "es2022", Value: core.ModuleKindES2022},
|
||||
{Key: "esnext", Value: core.ModuleKindESNext},
|
||||
{Key: "node16", Value: core.ModuleKindNode16},
|
||||
{Key: "node18", Value: core.ModuleKindNode18},
|
||||
{Key: "node20", Value: core.ModuleKindNode20},
|
||||
{Key: "nodenext", Value: core.ModuleKindNodeNext},
|
||||
{Key: "preserve", Value: core.ModuleKindPreserve},
|
||||
})
|
||||
|
||||
var moduleDetectionOptionMap = collections.NewOrderedMapFromList([]collections.MapEntry[string, any]{
|
||||
{Key: "auto", Value: core.ModuleDetectionKindAuto},
|
||||
{Key: "legacy", Value: core.ModuleDetectionKindLegacy},
|
||||
{Key: "force", Value: core.ModuleDetectionKindForce},
|
||||
})
|
||||
|
||||
var jsxOptionMap = collections.NewOrderedMapFromList([]collections.MapEntry[string, any]{
|
||||
{Key: "preserve", Value: core.JsxEmitPreserve},
|
||||
{Key: "react-native", Value: core.JsxEmitReactNative},
|
||||
{Key: "react-jsx", Value: core.JsxEmitReactJSX},
|
||||
{Key: "react-jsxdev", Value: core.JsxEmitReactJSXDev},
|
||||
{Key: "react", Value: core.JsxEmitReact},
|
||||
})
|
||||
|
||||
var newLineOptionMap = collections.NewOrderedMapFromList([]collections.MapEntry[string, any]{
|
||||
{Key: "crlf", Value: core.NewLineKindCRLF},
|
||||
{Key: "lf", Value: core.NewLineKindLF},
|
||||
})
|
||||
|
||||
var targetToLibMap = map[core.ScriptTarget]string{
|
||||
core.ScriptTargetESNext: "lib.esnext.full.d.ts",
|
||||
core.ScriptTargetES2025: "lib.es2025.full.d.ts",
|
||||
core.ScriptTargetES2024: "lib.es2024.full.d.ts",
|
||||
core.ScriptTargetES2023: "lib.es2023.full.d.ts",
|
||||
core.ScriptTargetES2022: "lib.es2022.full.d.ts",
|
||||
core.ScriptTargetES2021: "lib.es2021.full.d.ts",
|
||||
core.ScriptTargetES2020: "lib.es2020.full.d.ts",
|
||||
core.ScriptTargetES2019: "lib.es2019.full.d.ts",
|
||||
core.ScriptTargetES2018: "lib.es2018.full.d.ts",
|
||||
core.ScriptTargetES2017: "lib.es2017.full.d.ts",
|
||||
core.ScriptTargetES2016: "lib.es2016.full.d.ts",
|
||||
core.ScriptTargetES2015: "lib.es6.d.ts", // We don't use lib.es2015.full.d.ts due to breaking change.
|
||||
}
|
||||
|
||||
func TargetToLibMap() map[core.ScriptTarget]string {
|
||||
return targetToLibMap
|
||||
}
|
||||
|
||||
func GetDefaultLibFileName(options *core.CompilerOptions) string {
|
||||
name, ok := targetToLibMap[options.GetEmitScriptTarget()]
|
||||
if !ok {
|
||||
return "lib.d.ts"
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
var watchFileEnumMap = collections.NewOrderedMapFromList([]collections.MapEntry[string, any]{
|
||||
{Key: "fixedpollinginterval", Value: core.WatchFileKindFixedPollingInterval},
|
||||
{Key: "prioritypollinginterval", Value: core.WatchFileKindPriorityPollingInterval},
|
||||
{Key: "dynamicprioritypolling", Value: core.WatchFileKindDynamicPriorityPolling},
|
||||
{Key: "fixedchunksizepolling", Value: core.WatchFileKindFixedChunkSizePolling},
|
||||
{Key: "usefsevents", Value: core.WatchFileKindUseFsEvents},
|
||||
{Key: "usefseventsonparentdirectory", Value: core.WatchFileKindUseFsEventsOnParentDirectory},
|
||||
})
|
||||
|
||||
var watchDirectoryEnumMap = collections.NewOrderedMapFromList([]collections.MapEntry[string, any]{
|
||||
{Key: "usefsevents", Value: core.WatchDirectoryKindUseFsEvents},
|
||||
{Key: "fixedpollinginterval", Value: core.WatchDirectoryKindFixedPollingInterval},
|
||||
{Key: "dynamicprioritypolling", Value: core.WatchDirectoryKindDynamicPriorityPolling},
|
||||
{Key: "fixedchunksizepolling", Value: core.WatchDirectoryKindFixedChunkSizePolling},
|
||||
})
|
||||
|
||||
var fallbackEnumMap = collections.NewOrderedMapFromList([]collections.MapEntry[string, any]{
|
||||
{Key: "fixedinterval", Value: core.PollingKindFixedInterval},
|
||||
{Key: "priorityinterval", Value: core.PollingKindPriorityInterval},
|
||||
{Key: "dynamicpriority", Value: core.PollingKindDynamicPriority},
|
||||
{Key: "fixedchunksize", Value: core.PollingKindFixedChunkSize},
|
||||
})
|
||||
120
tools/tsgo/internal/tsoptions/errors.go
Normal file
120
tools/tsgo/internal/tsoptions/errors.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package tsoptions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/ast"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/diagnostics"
|
||||
"github.com/microsoft/typescript-go/internal/scanner"
|
||||
)
|
||||
|
||||
func createDiagnosticForInvalidEnumType(opt *CommandLineOption, sourceFile *ast.SourceFile, node *ast.Node) *ast.Diagnostic {
|
||||
namesOfType := slices.Collect(opt.EnumMap().Keys())
|
||||
stringNames := formatEnumTypeKeys(opt, namesOfType)
|
||||
optName := "--" + opt.Name
|
||||
return CreateDiagnosticForNodeInSourceFileOrCompilerDiagnostic(sourceFile, node, diagnostics.Argument_for_0_option_must_be_Colon_1, optName, stringNames)
|
||||
}
|
||||
|
||||
func formatEnumTypeKeys(opt *CommandLineOption, keys []string) string {
|
||||
if opt.DeprecatedKeys() != nil {
|
||||
keys = core.Filter(keys, func(key string) bool { return !opt.DeprecatedKeys().Has(key) })
|
||||
}
|
||||
return "'" + strings.Join(keys, "', '") + "'"
|
||||
}
|
||||
|
||||
func getCompilerOptionValueTypeString(option *CommandLineOption) string {
|
||||
switch option.Kind {
|
||||
case CommandLineOptionTypeListOrElement:
|
||||
return fmt.Sprintf("%v or Array", getCompilerOptionValueTypeString(option.Elements()))
|
||||
case CommandLineOptionTypeList:
|
||||
return "Array"
|
||||
default:
|
||||
return string(option.Kind)
|
||||
}
|
||||
}
|
||||
|
||||
func (parser *commandLineParser) createUnknownOptionError(
|
||||
unknownOption string,
|
||||
unknownOptionErrorText string,
|
||||
node *ast.Node,
|
||||
sourceFile *ast.SourceFile,
|
||||
) *ast.Diagnostic {
|
||||
return createUnknownOptionError(
|
||||
unknownOption,
|
||||
parser.UnknownOptionDiagnostic(),
|
||||
unknownOptionErrorText,
|
||||
node,
|
||||
sourceFile,
|
||||
parser.AlternateMode(),
|
||||
)
|
||||
}
|
||||
|
||||
func createUnknownOptionError(
|
||||
unknownOption string,
|
||||
unknownOptionDiagnostic *diagnostics.Message,
|
||||
unknownOptionErrorText string, // optional
|
||||
node *ast.Node, // optional
|
||||
sourceFile *ast.SourceFile, // optional
|
||||
alternateMode *AlternateModeDiagnostics, // optional
|
||||
) *ast.Diagnostic {
|
||||
if alternateMode != nil && alternateMode.optionsNameMap != nil {
|
||||
otherOption := alternateMode.optionsNameMap.Get(strings.ToLower(unknownOption))
|
||||
if otherOption != nil {
|
||||
// tscbuildoption
|
||||
diagnostic := alternateMode.diagnostic
|
||||
if otherOption.Name == "build" {
|
||||
diagnostic = diagnostics.Option_build_must_be_the_first_command_line_argument
|
||||
}
|
||||
return CreateDiagnosticForNodeInSourceFileOrCompilerDiagnostic(sourceFile, node, diagnostic, unknownOption)
|
||||
}
|
||||
}
|
||||
if unknownOptionErrorText == "" {
|
||||
unknownOptionErrorText = unknownOption
|
||||
}
|
||||
// TODO: possibleOption := spelling suggestion
|
||||
return CreateDiagnosticForNodeInSourceFileOrCompilerDiagnostic(sourceFile, node, unknownOptionDiagnostic, unknownOptionErrorText)
|
||||
}
|
||||
|
||||
func CreateDiagnosticForNodeInSourceFile(sourceFile *ast.SourceFile, node *ast.Node, message *diagnostics.Message, args ...any) *ast.Diagnostic {
|
||||
return ast.NewDiagnostic(sourceFile, core.NewTextRange(scanner.SkipTrivia(sourceFile.Text(), node.Loc.Pos()), node.End()), message, args...)
|
||||
}
|
||||
|
||||
func CreateDiagnosticForNodeInSourceFileOrCompilerDiagnostic(sourceFile *ast.SourceFile, node *ast.Node, message *diagnostics.Message, args ...any) *ast.Diagnostic {
|
||||
if sourceFile != nil && node != nil {
|
||||
return CreateDiagnosticForNodeInSourceFile(sourceFile, node, message, args...)
|
||||
}
|
||||
return ast.NewCompilerDiagnostic(message, args...)
|
||||
}
|
||||
|
||||
func extraKeyDiagnostics(s string) *diagnostics.Message {
|
||||
switch s {
|
||||
case "compilerOptions":
|
||||
return diagnostics.Unknown_compiler_option_0
|
||||
case "watchOptions":
|
||||
return diagnostics.Unknown_watch_option_0
|
||||
case "typeAcquisition":
|
||||
return diagnostics.Unknown_type_acquisition_option_0
|
||||
case "buildOptions":
|
||||
return diagnostics.Unknown_build_option_0
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func extraKeyDidYouMeanDiagnostics(s string) *diagnostics.Message {
|
||||
switch s {
|
||||
case "compilerOptions":
|
||||
return diagnostics.Unknown_compiler_option_0_Did_you_mean_1
|
||||
case "watchOptions":
|
||||
return diagnostics.Unknown_watch_option_0_Did_you_mean_1
|
||||
case "typeAcquisition":
|
||||
return diagnostics.Unknown_type_acquisition_option_0_Did_you_mean_1
|
||||
case "buildOptions":
|
||||
return diagnostics.Unknown_build_option_0_Did_you_mean_1
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
51
tools/tsgo/internal/tsoptions/export_test.go
Normal file
51
tools/tsgo/internal/tsoptions/export_test.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package tsoptions
|
||||
|
||||
import (
|
||||
"github.com/microsoft/typescript-go/internal/ast"
|
||||
"github.com/microsoft/typescript-go/internal/collections"
|
||||
"github.com/microsoft/typescript-go/internal/vfs"
|
||||
)
|
||||
|
||||
func getTestParseCommandLineWorkerDiagnostics(decls []*CommandLineOption) *ParseCommandLineWorkerDiagnostics {
|
||||
if len(decls) == 0 {
|
||||
return CompilerOptionsDidYouMeanDiagnostics
|
||||
}
|
||||
return getParseCommandLineWorkerDiagnostics(decls)
|
||||
}
|
||||
|
||||
func ParseCommandLineTestWorker(
|
||||
decls []*CommandLineOption,
|
||||
commandLine []string,
|
||||
fs vfs.FS,
|
||||
currentDirectory string,
|
||||
) *TestCommandLineParser {
|
||||
parser := &commandLineParser{
|
||||
fs: fs,
|
||||
currentDirectory: currentDirectory,
|
||||
workerDiagnostics: CompilerOptionsDidYouMeanDiagnostics,
|
||||
fileNames: []string{},
|
||||
options: &collections.OrderedMap[string, any]{},
|
||||
errors: []*ast.Diagnostic{},
|
||||
}
|
||||
if len(decls) != 0 {
|
||||
parser.workerDiagnostics = getTestParseCommandLineWorkerDiagnostics(decls)
|
||||
}
|
||||
|
||||
parser.optionsMap = GetNameMapFromList(parser.OptionsDeclarations())
|
||||
parser.parseStrings(commandLine)
|
||||
return &TestCommandLineParser{
|
||||
Fs: fs,
|
||||
WorkerDiagnostics: parser.workerDiagnostics,
|
||||
FileNames: parser.fileNames,
|
||||
Options: parser.options,
|
||||
Errors: parser.errors,
|
||||
}
|
||||
}
|
||||
|
||||
type TestCommandLineParser struct {
|
||||
Fs vfs.FS
|
||||
WorkerDiagnostics *ParseCommandLineWorkerDiagnostics
|
||||
FileNames []string
|
||||
Options *collections.OrderedMap[string, any]
|
||||
Errors []*ast.Diagnostic
|
||||
}
|
||||
58
tools/tsgo/internal/tsoptions/namemap.go
Normal file
58
tools/tsgo/internal/tsoptions/namemap.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package tsoptions
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/collections"
|
||||
)
|
||||
|
||||
var (
|
||||
CompilerNameMap = GetNameMapFromList(OptionsDeclarations)
|
||||
BuildNameMap = GetNameMapFromList(BuildOpts)
|
||||
WatchNameMap = GetNameMapFromList(OptionsForWatch)
|
||||
)
|
||||
|
||||
func GetNameMapFromList(optDecls []*CommandLineOption) *NameMap {
|
||||
optionsNames := collections.NewOrderedMapWithSizeHint[string, *CommandLineOption](len(optDecls))
|
||||
shortOptionNames := map[string]string{}
|
||||
for _, option := range optDecls {
|
||||
optionsNames.Set(strings.ToLower(option.Name), option)
|
||||
if option.ShortName != "" {
|
||||
shortOptionNames[option.ShortName] = option.Name
|
||||
}
|
||||
}
|
||||
return &NameMap{
|
||||
optionsNames: optionsNames,
|
||||
shortOptionNames: shortOptionNames,
|
||||
}
|
||||
}
|
||||
|
||||
type NameMap struct {
|
||||
optionsNames *collections.OrderedMap[string, *CommandLineOption]
|
||||
shortOptionNames map[string]string
|
||||
}
|
||||
|
||||
func (nm *NameMap) Get(name string) *CommandLineOption {
|
||||
return nm.optionsNames.GetOrZero(strings.ToLower(name))
|
||||
}
|
||||
|
||||
func (nm *NameMap) GetFromShort(shortName string) *CommandLineOption {
|
||||
// returns option only if shortName is a valid short option
|
||||
name, ok := nm.shortOptionNames[shortName]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return nm.Get(name)
|
||||
}
|
||||
|
||||
func (nm *NameMap) GetOptionDeclarationFromName(optionName string, allowShort bool) *CommandLineOption {
|
||||
optionName = strings.ToLower(optionName)
|
||||
// Try to translate short option names to their full equivalents.
|
||||
if allowShort {
|
||||
short := nm.shortOptionNames[optionName]
|
||||
if short != "" {
|
||||
optionName = short
|
||||
}
|
||||
}
|
||||
return nm.Get(optionName)
|
||||
}
|
||||
45
tools/tsgo/internal/tsoptions/parsedbuildcommandline.go
Normal file
45
tools/tsgo/internal/tsoptions/parsedbuildcommandline.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package tsoptions
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/ast"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/locale"
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
)
|
||||
|
||||
type ParsedBuildCommandLine struct {
|
||||
BuildOptions *core.BuildOptions `json:"buildOptions"`
|
||||
CompilerOptions *core.CompilerOptions `json:"compilerOptions"`
|
||||
WatchOptions *core.WatchOptions `json:"watchOptions"`
|
||||
Projects []string `json:"projects"`
|
||||
Errors []*ast.Diagnostic `json:"errors"`
|
||||
Raw any `json:"raw"`
|
||||
|
||||
comparePathsOptions tspath.ComparePathsOptions
|
||||
|
||||
resolvedProjectPaths []string
|
||||
resolvedProjectPathsOnce sync.Once
|
||||
|
||||
locale locale.Locale
|
||||
localeOnce sync.Once
|
||||
}
|
||||
|
||||
func (p *ParsedBuildCommandLine) ResolvedProjectPaths() []string {
|
||||
p.resolvedProjectPathsOnce.Do(func() {
|
||||
p.resolvedProjectPaths = core.Map(p.Projects, func(project string) string {
|
||||
return core.ResolveConfigFileNameOfProjectReference(
|
||||
tspath.ResolvePath(p.comparePathsOptions.CurrentDirectory, project),
|
||||
)
|
||||
})
|
||||
})
|
||||
return p.resolvedProjectPaths
|
||||
}
|
||||
|
||||
func (p *ParsedBuildCommandLine) Locale() locale.Locale {
|
||||
p.localeOnce.Do(func() {
|
||||
p.locale, _ = locale.Parse(p.CompilerOptions.Locale)
|
||||
})
|
||||
return p.locale
|
||||
}
|
||||
423
tools/tsgo/internal/tsoptions/parsedcommandline.go
Normal file
423
tools/tsgo/internal/tsoptions/parsedcommandline.go
Normal file
@@ -0,0 +1,423 @@
|
||||
package tsoptions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"iter"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/ast"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/diagnostics"
|
||||
"github.com/microsoft/typescript-go/internal/glob"
|
||||
"github.com/microsoft/typescript-go/internal/locale"
|
||||
"github.com/microsoft/typescript-go/internal/module"
|
||||
"github.com/microsoft/typescript-go/internal/outputpaths"
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
"github.com/microsoft/typescript-go/internal/vfs"
|
||||
"github.com/microsoft/typescript-go/internal/vfs/vfsmatch"
|
||||
)
|
||||
|
||||
const (
|
||||
fileGlobPattern = "*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,json}"
|
||||
recursiveFileGlobPattern = "**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,json}"
|
||||
)
|
||||
|
||||
type ParsedCommandLine struct {
|
||||
ParsedConfig *core.ParsedOptions `json:"parsedConfig"`
|
||||
|
||||
ConfigFile *TsConfigSourceFile `json:"configFile"` // TsConfigSourceFile, used in Program and ExecuteCommandLine
|
||||
Errors []*ast.Diagnostic `json:"errors"`
|
||||
Raw any `json:"raw"`
|
||||
CompileOnSave *bool `json:"compileOnSave"`
|
||||
|
||||
comparePathsOptions tspath.ComparePathsOptions
|
||||
wildcardDirectoriesOnce sync.Once
|
||||
wildcardDirectories map[string]bool
|
||||
includeGlobsOnce sync.Once
|
||||
includeGlobs []*glob.Glob
|
||||
extraFileExtensions []FileExtensionInfo
|
||||
|
||||
sourceAndOutputMapsOnce sync.Once
|
||||
sourceToProjectReference map[tspath.Path]*SourceOutputAndProjectReference
|
||||
outputDtsToProjectReference map[tspath.Path]*SourceOutputAndProjectReference
|
||||
|
||||
commonSourceDirectory string
|
||||
commonSourceDirectoryOnce sync.Once
|
||||
|
||||
resolvedProjectReferencePaths []string
|
||||
resolvedProjectReferencePathsOnce sync.Once
|
||||
|
||||
literalFileNamesLen int
|
||||
fileNamesByPath map[tspath.Path]string // maps file names to their paths, used for quick lookups
|
||||
fileNamesByPathOnce sync.Once
|
||||
|
||||
locale locale.Locale
|
||||
localeOnce sync.Once
|
||||
}
|
||||
|
||||
func NewParsedCommandLine(
|
||||
compilerOptions *core.CompilerOptions,
|
||||
rootFileNames []string,
|
||||
comparePathsOptions tspath.ComparePathsOptions,
|
||||
) *ParsedCommandLine {
|
||||
return &ParsedCommandLine{
|
||||
ParsedConfig: &core.ParsedOptions{
|
||||
CompilerOptions: compilerOptions,
|
||||
FileNames: rootFileNames,
|
||||
},
|
||||
comparePathsOptions: comparePathsOptions,
|
||||
}
|
||||
}
|
||||
|
||||
type SourceOutputAndProjectReference struct {
|
||||
Source string
|
||||
OutputDts string
|
||||
Resolved *ParsedCommandLine
|
||||
}
|
||||
|
||||
var (
|
||||
_ module.ResolvedProjectReference = (*ParsedCommandLine)(nil)
|
||||
_ outputpaths.OutputPathsHost = (*ParsedCommandLine)(nil)
|
||||
)
|
||||
|
||||
func (p *ParsedCommandLine) ConfigName() string {
|
||||
if p == nil || p.ConfigFile == nil {
|
||||
return ""
|
||||
}
|
||||
return p.ConfigFile.SourceFile.FileName()
|
||||
}
|
||||
|
||||
func (p *ParsedCommandLine) SourceToProjectReference() map[tspath.Path]*SourceOutputAndProjectReference {
|
||||
return p.sourceToProjectReference
|
||||
}
|
||||
|
||||
func (p *ParsedCommandLine) OutputDtsToProjectReference() map[tspath.Path]*SourceOutputAndProjectReference {
|
||||
return p.outputDtsToProjectReference
|
||||
}
|
||||
|
||||
func (p *ParsedCommandLine) ParseInputOutputNames() {
|
||||
p.sourceAndOutputMapsOnce.Do(func() {
|
||||
sourceToOutput := map[tspath.Path]*SourceOutputAndProjectReference{}
|
||||
outputDtsToSource := map[tspath.Path]*SourceOutputAndProjectReference{}
|
||||
|
||||
for outputDts, source := range p.getOutputDeclarationAndSourceFileNames() {
|
||||
path := tspath.ToPath(source, p.GetCurrentDirectory(), p.UseCaseSensitiveFileNames())
|
||||
projectReference := &SourceOutputAndProjectReference{
|
||||
Source: source,
|
||||
OutputDts: outputDts,
|
||||
Resolved: p,
|
||||
}
|
||||
if outputDts != "" {
|
||||
outputDtsToSource[tspath.ToPath(outputDts, p.GetCurrentDirectory(), p.UseCaseSensitiveFileNames())] = projectReference
|
||||
}
|
||||
sourceToOutput[path] = projectReference
|
||||
}
|
||||
p.outputDtsToProjectReference = outputDtsToSource
|
||||
p.sourceToProjectReference = sourceToOutput
|
||||
})
|
||||
}
|
||||
|
||||
func (p *ParsedCommandLine) CommonSourceDirectory() string {
|
||||
p.commonSourceDirectoryOnce.Do(func() {
|
||||
files := func() []string {
|
||||
return core.Filter(p.ParsedConfig.FileNames, func(file string) bool {
|
||||
return !(p.ParsedConfig.CompilerOptions.NoEmitForJsFiles.IsTrue() && tspath.HasJSFileExtension(file)) && !tspath.IsDeclarationFileName(file)
|
||||
})
|
||||
}
|
||||
|
||||
p.commonSourceDirectory = outputpaths.GetCommonSourceDirectory(
|
||||
p.ParsedConfig.CompilerOptions,
|
||||
files,
|
||||
p.GetCurrentDirectory(),
|
||||
p.UseCaseSensitiveFileNames(),
|
||||
p.checkSourceFilesBelongToPath,
|
||||
)
|
||||
})
|
||||
return p.commonSourceDirectory
|
||||
}
|
||||
|
||||
func (p *ParsedCommandLine) checkSourceFilesBelongToPath(sourceFiles []string, rootDirectory string) bool {
|
||||
allFilesBelongToPath := true
|
||||
for _, file := range sourceFiles {
|
||||
absoluteSourceFilePath := tspath.GetCanonicalFileName(tspath.GetNormalizedAbsolutePath(file, p.GetCurrentDirectory()), p.UseCaseSensitiveFileNames())
|
||||
if !tspath.ContainsPath(rootDirectory, file, p.comparePathsOptions) {
|
||||
p.Errors = append(p.Errors, ast.NewCompilerDiagnostic(diagnostics.File_0_is_not_under_rootDir_1_rootDir_is_expected_to_contain_all_source_files, absoluteSourceFilePath, rootDirectory))
|
||||
allFilesBelongToPath = false
|
||||
}
|
||||
}
|
||||
|
||||
return allFilesBelongToPath
|
||||
}
|
||||
|
||||
func (p *ParsedCommandLine) GetCurrentDirectory() string {
|
||||
return p.comparePathsOptions.CurrentDirectory
|
||||
}
|
||||
|
||||
func (p *ParsedCommandLine) UseCaseSensitiveFileNames() bool {
|
||||
return p.comparePathsOptions.UseCaseSensitiveFileNames
|
||||
}
|
||||
|
||||
func (p *ParsedCommandLine) getOutputDeclarationAndSourceFileNames() iter.Seq2[string, string] {
|
||||
return func(yield func(dtsName string, inputName string) bool) {
|
||||
for _, fileName := range p.ParsedConfig.FileNames {
|
||||
var outputDts string
|
||||
if !tspath.IsDeclarationFileName(fileName) && !tspath.FileExtensionIs(fileName, tspath.ExtensionJson) {
|
||||
outputDts = outputpaths.GetOutputDeclarationFileNameWorker(fileName, p.CompilerOptions(), p)
|
||||
}
|
||||
if !yield(outputDts, fileName) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ParsedCommandLine) GetOutputFileNames() iter.Seq[string] {
|
||||
return func(yield func(outputName string) bool) {
|
||||
for _, fileName := range p.ParsedConfig.FileNames {
|
||||
if tspath.IsDeclarationFileName(fileName) {
|
||||
continue
|
||||
}
|
||||
jsFileName := outputpaths.GetOutputJSFileName(fileName, p.CompilerOptions(), p)
|
||||
isJson := tspath.FileExtensionIs(fileName, tspath.ExtensionJson)
|
||||
if jsFileName != "" {
|
||||
if !yield(jsFileName) {
|
||||
return
|
||||
}
|
||||
if !isJson {
|
||||
sourceMap := outputpaths.GetSourceMapFilePath(jsFileName, p.CompilerOptions())
|
||||
if sourceMap != "" {
|
||||
if !yield(sourceMap) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if isJson {
|
||||
continue
|
||||
}
|
||||
if p.CompilerOptions().GetEmitDeclarations() {
|
||||
dtsFileName := outputpaths.GetOutputDeclarationFileNameWorker(fileName, p.CompilerOptions(), p)
|
||||
if dtsFileName != "" {
|
||||
if !yield(dtsFileName) {
|
||||
return
|
||||
}
|
||||
if p.CompilerOptions().GetAreDeclarationMapsEnabled() {
|
||||
declarationMap := dtsFileName + ".map"
|
||||
if !yield(declarationMap) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ParsedCommandLine) GetBuildInfoFileName() string {
|
||||
return outputpaths.GetBuildInfoFileName(p.CompilerOptions(), p.comparePathsOptions)
|
||||
}
|
||||
|
||||
// WildcardDirectories returns the cached wildcard directories, initializing them if needed
|
||||
func (p *ParsedCommandLine) WildcardDirectories() map[string]bool {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
p.wildcardDirectoriesOnce.Do(func() {
|
||||
if p.wildcardDirectories == nil {
|
||||
p.wildcardDirectories = getWildcardDirectories(
|
||||
p.ConfigFile.configFileSpecs.validatedIncludeSpecs,
|
||||
p.ConfigFile.configFileSpecs.validatedExcludeSpecs,
|
||||
p.comparePathsOptions,
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
return p.wildcardDirectories
|
||||
}
|
||||
|
||||
func (p *ParsedCommandLine) WildcardDirectoryGlobs() []*glob.Glob {
|
||||
wildcardDirectories := p.WildcardDirectories()
|
||||
if wildcardDirectories == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
p.includeGlobsOnce.Do(func() {
|
||||
if p.includeGlobs == nil {
|
||||
globs := make([]*glob.Glob, 0, len(wildcardDirectories))
|
||||
for dir, recursive := range wildcardDirectories {
|
||||
if parsed, err := glob.Parse(fmt.Sprintf("%s/%s", tspath.NormalizePath(dir), core.IfElse(recursive, recursiveFileGlobPattern, fileGlobPattern))); err == nil {
|
||||
globs = append(globs, parsed)
|
||||
}
|
||||
}
|
||||
p.includeGlobs = globs
|
||||
}
|
||||
})
|
||||
|
||||
return p.includeGlobs
|
||||
}
|
||||
|
||||
// Normalized file names explicitly specified in `files`
|
||||
func (p *ParsedCommandLine) LiteralFileNames() []string {
|
||||
if p != nil && p.ConfigFile != nil {
|
||||
return p.FileNames()[0:p.literalFileNamesLen]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *ParsedCommandLine) SetParsedOptions(o *core.ParsedOptions) {
|
||||
p.ParsedConfig = o
|
||||
}
|
||||
|
||||
func (p *ParsedCommandLine) SetCompilerOptions(o *core.CompilerOptions) {
|
||||
p.ParsedConfig.CompilerOptions = o
|
||||
}
|
||||
|
||||
func (p *ParsedCommandLine) CompilerOptions() *core.CompilerOptions {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
return p.ParsedConfig.CompilerOptions
|
||||
}
|
||||
|
||||
func (p *ParsedCommandLine) SetTypeAcquisition(o *core.TypeAcquisition) {
|
||||
p.ParsedConfig.TypeAcquisition = o
|
||||
}
|
||||
|
||||
func (p *ParsedCommandLine) TypeAcquisition() *core.TypeAcquisition {
|
||||
return p.ParsedConfig.TypeAcquisition
|
||||
}
|
||||
|
||||
// All file names matched by files, include, and exclude patterns
|
||||
func (p *ParsedCommandLine) FileNames() []string {
|
||||
return p.ParsedConfig.FileNames
|
||||
}
|
||||
|
||||
func (p *ParsedCommandLine) FileNamesByPath() map[tspath.Path]string {
|
||||
p.fileNamesByPathOnce.Do(func() {
|
||||
p.fileNamesByPath = make(map[tspath.Path]string, len(p.ParsedConfig.FileNames))
|
||||
for _, fileName := range p.ParsedConfig.FileNames {
|
||||
path := tspath.ToPath(fileName, p.GetCurrentDirectory(), p.UseCaseSensitiveFileNames())
|
||||
p.fileNamesByPath[path] = fileName
|
||||
}
|
||||
})
|
||||
return p.fileNamesByPath
|
||||
}
|
||||
|
||||
func (p *ParsedCommandLine) ProjectReferences() []*core.ProjectReference {
|
||||
return p.ParsedConfig.ProjectReferences
|
||||
}
|
||||
|
||||
func (p *ParsedCommandLine) ResolvedProjectReferencePaths() []string {
|
||||
p.resolvedProjectReferencePathsOnce.Do(func() {
|
||||
p.resolvedProjectReferencePaths = core.Map(p.ParsedConfig.ProjectReferences, core.ResolveProjectReferencePath)
|
||||
})
|
||||
return p.resolvedProjectReferencePaths
|
||||
}
|
||||
|
||||
func (p *ParsedCommandLine) ExtendedSourceFiles() []string {
|
||||
if p == nil || p.ConfigFile == nil {
|
||||
return nil
|
||||
}
|
||||
return p.ConfigFile.ExtendedSourceFiles
|
||||
}
|
||||
|
||||
func (p *ParsedCommandLine) GetConfigFileParsingDiagnostics() []*ast.Diagnostic {
|
||||
if p.ConfigFile != nil {
|
||||
// todo: !!! should be ConfigFile.ParseDiagnostics, check if they are the same
|
||||
return slices.Concat(p.ConfigFile.SourceFile.Diagnostics(), p.Errors)
|
||||
}
|
||||
return p.Errors
|
||||
}
|
||||
|
||||
// PossiblyMatchesFileName is a fast check to see if a file is currently included by a config
|
||||
// or would be included if the file were to be created. It may return false positives.
|
||||
func (p *ParsedCommandLine) PossiblyMatchesFileName(fileName string) bool {
|
||||
path := tspath.ToPath(fileName, p.GetCurrentDirectory(), p.UseCaseSensitiveFileNames())
|
||||
if _, ok := p.FileNamesByPath()[path]; ok {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, include := range p.ConfigFile.configFileSpecs.validatedIncludeSpecs {
|
||||
if !strings.ContainsAny(include, "*?") && !vfsmatch.IsImplicitGlob(include) {
|
||||
includePath := tspath.ToPath(include, p.GetCurrentDirectory(), p.UseCaseSensitiveFileNames())
|
||||
if includePath == path {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
if wildcardDirectoryGlobs := p.WildcardDirectoryGlobs(); len(wildcardDirectoryGlobs) > 0 {
|
||||
for _, glob := range wildcardDirectoryGlobs {
|
||||
if glob.Match(fileName) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *ParsedCommandLine) PossiblyMatchesDirectoryName(directoryPath tspath.Path) bool {
|
||||
for wildcardDir, recursive := range p.WildcardDirectories() {
|
||||
wildcardDirPath := tspath.ToPath(wildcardDir, p.GetCurrentDirectory(), p.UseCaseSensitiveFileNames())
|
||||
if recursive {
|
||||
if wildcardDirPath.ContainsPath(directoryPath) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if wildcardDirPath == directoryPath {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *ParsedCommandLine) GetMatchedFileSpec(fileName string) string {
|
||||
return p.ConfigFile.configFileSpecs.getMatchedFileSpec(fileName, p.comparePathsOptions)
|
||||
}
|
||||
|
||||
func (p *ParsedCommandLine) GetMatchedIncludeSpec(fileName string) (string, bool) {
|
||||
if len(p.ConfigFile.configFileSpecs.validatedIncludeSpecs) == 0 {
|
||||
return "", false
|
||||
}
|
||||
|
||||
if p.ConfigFile.configFileSpecs.isDefaultIncludeSpec {
|
||||
return p.ConfigFile.configFileSpecs.validatedIncludeSpecs[0], true
|
||||
}
|
||||
|
||||
return p.ConfigFile.configFileSpecs.getMatchedIncludeSpec(fileName, p.comparePathsOptions), false
|
||||
}
|
||||
|
||||
func (p *ParsedCommandLine) ReloadFileNamesOfParsedCommandLine(fs vfs.FS) *ParsedCommandLine {
|
||||
parsedConfig := *p.ParsedConfig
|
||||
fileNames, literalFileNamesLen := getFileNamesFromConfigSpecs(
|
||||
*p.ConfigFile.configFileSpecs,
|
||||
p.GetCurrentDirectory(),
|
||||
p.CompilerOptions(),
|
||||
fs,
|
||||
p.extraFileExtensions,
|
||||
)
|
||||
parsedConfig.FileNames = fileNames
|
||||
parsedCommandLine := ParsedCommandLine{
|
||||
ParsedConfig: &parsedConfig,
|
||||
ConfigFile: p.ConfigFile,
|
||||
Errors: p.Errors,
|
||||
Raw: p.Raw,
|
||||
CompileOnSave: p.CompileOnSave,
|
||||
comparePathsOptions: p.comparePathsOptions,
|
||||
wildcardDirectories: p.wildcardDirectories,
|
||||
includeGlobs: p.includeGlobs,
|
||||
extraFileExtensions: p.extraFileExtensions,
|
||||
literalFileNamesLen: literalFileNamesLen,
|
||||
}
|
||||
return &parsedCommandLine
|
||||
}
|
||||
|
||||
func (p *ParsedCommandLine) Locale() locale.Locale {
|
||||
p.localeOnce.Do(func() {
|
||||
p.locale, _ = locale.Parse(p.CompilerOptions().Locale)
|
||||
})
|
||||
return p.locale
|
||||
}
|
||||
168
tools/tsgo/internal/tsoptions/parsedcommandline_test.go
Normal file
168
tools/tsgo/internal/tsoptions/parsedcommandline_test.go
Normal file
@@ -0,0 +1,168 @@
|
||||
package tsoptions_test
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/tsoptions"
|
||||
"github.com/microsoft/typescript-go/internal/tsoptions/tsoptionstest"
|
||||
"github.com/microsoft/typescript-go/internal/vfs/vfstest"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestParsedCommandLine(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("PossiblyMatchesFileName", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
noFiles := map[string]string{}
|
||||
noFilesFS := vfstest.FromMap(noFiles, true)
|
||||
|
||||
files := map[string]string{
|
||||
"/dev/a.ts": "",
|
||||
"/dev/a.d.ts": "",
|
||||
"/dev/a.js": "",
|
||||
"/dev/b.ts": "",
|
||||
"/dev/b.js": "",
|
||||
"/dev/c.d.ts": "",
|
||||
"/dev/z/a.ts": "",
|
||||
"/dev/z/abz.ts": "",
|
||||
"/dev/z/aba.ts": "",
|
||||
"/dev/z/b.ts": "",
|
||||
"/dev/z/bbz.ts": "",
|
||||
"/dev/z/bba.ts": "",
|
||||
"/dev/x/a.ts": "",
|
||||
"/dev/x/aa.ts": "",
|
||||
"/dev/x/b.ts": "",
|
||||
"/dev/x/y/a.ts": "",
|
||||
"/dev/x/y/b.ts": "",
|
||||
"/dev/js/a.js": "",
|
||||
"/dev/js/b.js": "",
|
||||
"/dev/js/d.min.js": "",
|
||||
"/dev/js/ab.min.js": "",
|
||||
"/ext/ext.ts": "",
|
||||
"/ext/b/a..b.ts": "",
|
||||
}
|
||||
|
||||
assertMatches := func(t *testing.T, parsedCommandLine *tsoptions.ParsedCommandLine, files map[string]string, matches []string) {
|
||||
t.Helper()
|
||||
for fileName := range files {
|
||||
actual := parsedCommandLine.PossiblyMatchesFileName(fileName)
|
||||
expected := slices.Contains(matches, fileName)
|
||||
assert.Equal(t, actual, expected, "fileName: %s", fileName)
|
||||
}
|
||||
for _, fileName := range matches {
|
||||
if _, ok := files[fileName]; !ok {
|
||||
actual := parsedCommandLine.PossiblyMatchesFileName(fileName)
|
||||
assert.Equal(t, actual, true, "fileName: %s", fileName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("with literal file list", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("without exclude", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
parsedCommandLine := tsoptionstest.GetParsedCommandLine(
|
||||
t,
|
||||
`{
|
||||
"files": [
|
||||
"a.ts",
|
||||
"b.ts"
|
||||
]
|
||||
}`,
|
||||
files,
|
||||
"/dev",
|
||||
/*useCaseSensitiveFileNames*/ true,
|
||||
)
|
||||
|
||||
assertMatches(t, parsedCommandLine, files, []string{
|
||||
"/dev/a.ts",
|
||||
"/dev/b.ts",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("are not removed due to excludes", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
parsedCommandLine := tsoptionstest.GetParsedCommandLine(
|
||||
t,
|
||||
`{
|
||||
"files": [
|
||||
"a.ts",
|
||||
"b.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"b.ts"
|
||||
]
|
||||
}`,
|
||||
files,
|
||||
"/dev",
|
||||
/*useCaseSensitiveFileNames*/ true,
|
||||
)
|
||||
|
||||
assertMatches(t, parsedCommandLine, files, []string{
|
||||
"/dev/a.ts",
|
||||
"/dev/b.ts",
|
||||
})
|
||||
|
||||
emptyParsedCommandLine := parsedCommandLine.ReloadFileNamesOfParsedCommandLine(noFilesFS)
|
||||
assertMatches(t, emptyParsedCommandLine, noFiles, []string{
|
||||
"/dev/a.ts",
|
||||
"/dev/b.ts",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("duplicates", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
parsedCommandLine := tsoptionstest.GetParsedCommandLine(
|
||||
t,
|
||||
`{
|
||||
"files": [
|
||||
"a.ts",
|
||||
"a.ts",
|
||||
"b.ts",
|
||||
]
|
||||
}`,
|
||||
files,
|
||||
"/dev",
|
||||
/*useCaseSensitiveFileNames*/ true,
|
||||
)
|
||||
|
||||
assert.DeepEqual(t, parsedCommandLine.LiteralFileNames(), []string{
|
||||
"/dev/a.ts",
|
||||
"/dev/b.ts",
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("with literal include list", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("without exclude", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
parsedCommandLine := tsoptionstest.GetParsedCommandLine(
|
||||
t,
|
||||
`{
|
||||
"include": [
|
||||
"a.ts",
|
||||
"b.ts"
|
||||
]
|
||||
}`,
|
||||
files,
|
||||
"/dev",
|
||||
/*useCaseSensitiveFileNames*/ true,
|
||||
)
|
||||
|
||||
assertMatches(t, parsedCommandLine, files, []string{
|
||||
"/dev/a.ts",
|
||||
"/dev/b.ts",
|
||||
})
|
||||
|
||||
emptyParsedCommandLine := parsedCommandLine.ReloadFileNamesOfParsedCommandLine(noFilesFS)
|
||||
assertMatches(t, emptyParsedCommandLine, noFiles, []string{
|
||||
"/dev/a.ts",
|
||||
"/dev/b.ts",
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
660
tools/tsgo/internal/tsoptions/parsinghelpers.go
Normal file
660
tools/tsgo/internal/tsoptions/parsinghelpers.go
Normal file
@@ -0,0 +1,660 @@
|
||||
package tsoptions
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"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/tspath"
|
||||
)
|
||||
|
||||
func ParseTristate(value any) core.Tristate {
|
||||
if value == nil {
|
||||
return core.TSUnknown
|
||||
}
|
||||
if v, ok := value.(core.Tristate); ok {
|
||||
return v
|
||||
}
|
||||
if value == true {
|
||||
return core.TSTrue
|
||||
} else {
|
||||
return core.TSFalse
|
||||
}
|
||||
}
|
||||
|
||||
func ParseStringArray(value any) []string {
|
||||
if arr, ok := value.([]any); ok {
|
||||
if arr == nil {
|
||||
return nil
|
||||
}
|
||||
result := make([]string, 0, len(arr))
|
||||
for _, v := range arr {
|
||||
if str, ok := v.(string); ok {
|
||||
result = append(result, str)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseStringMap(value any) *collections.OrderedMap[string, []string] {
|
||||
if m, ok := value.(*collections.OrderedMap[string, any]); ok {
|
||||
result := collections.NewOrderedMapWithSizeHint[string, []string](m.Size())
|
||||
for k, v := range m.Entries() {
|
||||
result.Set(k, ParseStringArray(v))
|
||||
}
|
||||
return result
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ParseString(value any) string {
|
||||
if str, ok := value.(string); ok {
|
||||
return str
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func parseNumber(value any) *int {
|
||||
if num, ok := value.(int); ok {
|
||||
return &num
|
||||
}
|
||||
if num, ok := value.(float64); ok {
|
||||
n := int(num)
|
||||
return &n
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseProjectReference(json any) []*core.ProjectReference {
|
||||
var result []*core.ProjectReference
|
||||
if v, ok := json.(*collections.OrderedMap[string, any]); ok {
|
||||
var reference core.ProjectReference
|
||||
if v, ok := v.Get("path"); ok {
|
||||
reference.Path = v.(string)
|
||||
}
|
||||
if v, ok := v.Get("circular"); ok {
|
||||
reference.Circular = v.(bool)
|
||||
}
|
||||
result = append(result, &reference)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func parseJsonToStringKey(json any) *collections.OrderedMap[string, any] {
|
||||
result := collections.NewOrderedMapWithSizeHint[string, any](6)
|
||||
if m, ok := json.(*collections.OrderedMap[string, any]); ok {
|
||||
if v, ok := m.Get("include"); ok {
|
||||
result.Set("include", v)
|
||||
}
|
||||
if v, ok := m.Get("exclude"); ok {
|
||||
result.Set("exclude", v)
|
||||
}
|
||||
if v, ok := m.Get("files"); ok {
|
||||
result.Set("files", v)
|
||||
}
|
||||
if v, ok := m.Get("references"); ok {
|
||||
result.Set("references", v)
|
||||
}
|
||||
if v, ok := m.Get("extends"); ok {
|
||||
if str, ok := v.(string); ok {
|
||||
result.Set("extends", []any{str})
|
||||
}
|
||||
result.Set("extends", v)
|
||||
}
|
||||
if v, ok := m.Get("compilerOptions"); ok {
|
||||
result.Set("compilerOptions", v)
|
||||
}
|
||||
if v, ok := m.Get("excludes"); ok {
|
||||
result.Set("excludes", v)
|
||||
}
|
||||
if v, ok := m.Get("typeAcquisition"); ok {
|
||||
result.Set("typeAcquisition", v)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
type optionParser interface {
|
||||
ParseOption(key string, value any) []*ast.Diagnostic
|
||||
UnknownOptionDiagnostic() *diagnostics.Message
|
||||
UnknownDidYouMeanDiagnostic() *diagnostics.Message
|
||||
}
|
||||
|
||||
type compilerOptionsParser struct {
|
||||
*core.CompilerOptions
|
||||
}
|
||||
|
||||
func (o *compilerOptionsParser) ParseOption(key string, value any) []*ast.Diagnostic {
|
||||
return ParseCompilerOptions(key, value, o.CompilerOptions)
|
||||
}
|
||||
|
||||
func (o *compilerOptionsParser) UnknownOptionDiagnostic() *diagnostics.Message {
|
||||
return extraKeyDiagnostics("compilerOptions")
|
||||
}
|
||||
|
||||
func (o *compilerOptionsParser) UnknownDidYouMeanDiagnostic() *diagnostics.Message {
|
||||
return extraKeyDidYouMeanDiagnostics("compilerOptions")
|
||||
}
|
||||
|
||||
type watchOptionsParser struct {
|
||||
*core.WatchOptions
|
||||
}
|
||||
|
||||
func (o *watchOptionsParser) ParseOption(key string, value any) []*ast.Diagnostic {
|
||||
return ParseWatchOptions(key, value, o.WatchOptions)
|
||||
}
|
||||
|
||||
func (o *watchOptionsParser) UnknownOptionDiagnostic() *diagnostics.Message {
|
||||
return extraKeyDiagnostics("watchOptions")
|
||||
}
|
||||
|
||||
func (o *watchOptionsParser) UnknownDidYouMeanDiagnostic() *diagnostics.Message {
|
||||
return extraKeyDidYouMeanDiagnostics("watchOptions")
|
||||
}
|
||||
|
||||
type typeAcquisitionParser struct {
|
||||
*core.TypeAcquisition
|
||||
}
|
||||
|
||||
func (o *typeAcquisitionParser) ParseOption(key string, value any) []*ast.Diagnostic {
|
||||
return ParseTypeAcquisition(key, value, o.TypeAcquisition)
|
||||
}
|
||||
|
||||
func (o *typeAcquisitionParser) UnknownOptionDiagnostic() *diagnostics.Message {
|
||||
return extraKeyDiagnostics("typeAcquisition")
|
||||
}
|
||||
|
||||
func (o *typeAcquisitionParser) UnknownDidYouMeanDiagnostic() *diagnostics.Message {
|
||||
return extraKeyDidYouMeanDiagnostics("typeAcquisition")
|
||||
}
|
||||
|
||||
type buildOptionsParser struct {
|
||||
*core.BuildOptions
|
||||
}
|
||||
|
||||
func (o *buildOptionsParser) ParseOption(key string, value any) []*ast.Diagnostic {
|
||||
return ParseBuildOptions(key, value, o.BuildOptions)
|
||||
}
|
||||
|
||||
func (o *buildOptionsParser) UnknownOptionDiagnostic() *diagnostics.Message {
|
||||
return extraKeyDiagnostics("buildOptions")
|
||||
}
|
||||
|
||||
func (o *buildOptionsParser) UnknownDidYouMeanDiagnostic() *diagnostics.Message {
|
||||
return extraKeyDidYouMeanDiagnostics("buildOptions")
|
||||
}
|
||||
|
||||
func ParseCompilerOptions(key string, value any, allOptions *core.CompilerOptions) []*ast.Diagnostic {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
if allOptions == nil {
|
||||
return nil
|
||||
}
|
||||
parseCompilerOptions(key, value, allOptions)
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseCompilerOptions(key string, value any, allOptions *core.CompilerOptions) (foundKey bool) {
|
||||
option := CommandLineCompilerOptionsMap.Get(key)
|
||||
if option != nil {
|
||||
key = option.Name
|
||||
}
|
||||
switch key {
|
||||
case "allowJs":
|
||||
allOptions.AllowJs = ParseTristate(value)
|
||||
case "allowImportingTsExtensions":
|
||||
allOptions.AllowImportingTsExtensions = ParseTristate(value)
|
||||
case "allowSyntheticDefaultImports":
|
||||
allOptions.AllowSyntheticDefaultImports = ParseTristate(value)
|
||||
case "allowNonTsExtensions":
|
||||
allOptions.AllowNonTsExtensions = ParseTristate(value)
|
||||
case "allowUmdGlobalAccess":
|
||||
allOptions.AllowUmdGlobalAccess = ParseTristate(value)
|
||||
case "allowUnreachableCode":
|
||||
allOptions.AllowUnreachableCode = ParseTristate(value)
|
||||
case "allowUnusedLabels":
|
||||
allOptions.AllowUnusedLabels = ParseTristate(value)
|
||||
case "allowArbitraryExtensions":
|
||||
allOptions.AllowArbitraryExtensions = ParseTristate(value)
|
||||
case "alwaysStrict":
|
||||
allOptions.AlwaysStrict = ParseTristate(value)
|
||||
case "assumeChangesOnlyAffectDirectDependencies":
|
||||
allOptions.AssumeChangesOnlyAffectDirectDependencies = ParseTristate(value)
|
||||
case "baseUrl":
|
||||
allOptions.BaseUrl = ParseString(value)
|
||||
case "build":
|
||||
allOptions.Build = ParseTristate(value)
|
||||
case "checkJs":
|
||||
allOptions.CheckJs = ParseTristate(value)
|
||||
case "customConditions":
|
||||
allOptions.CustomConditions = ParseStringArray(value)
|
||||
case "composite":
|
||||
allOptions.Composite = ParseTristate(value)
|
||||
case "declarationDir":
|
||||
allOptions.DeclarationDir = ParseString(value)
|
||||
case "deduplicatePackages":
|
||||
allOptions.DeduplicatePackages = ParseTristate(value)
|
||||
case "diagnostics":
|
||||
allOptions.Diagnostics = ParseTristate(value)
|
||||
case "disableSizeLimit":
|
||||
allOptions.DisableSizeLimit = ParseTristate(value)
|
||||
case "disableSourceOfProjectReferenceRedirect":
|
||||
allOptions.DisableSourceOfProjectReferenceRedirect = ParseTristate(value)
|
||||
case "disableSolutionSearching":
|
||||
allOptions.DisableSolutionSearching = ParseTristate(value)
|
||||
case "disableReferencedProjectLoad":
|
||||
allOptions.DisableReferencedProjectLoad = ParseTristate(value)
|
||||
case "declarationMap":
|
||||
allOptions.DeclarationMap = ParseTristate(value)
|
||||
case "declaration":
|
||||
allOptions.Declaration = ParseTristate(value)
|
||||
case "downlevelIteration":
|
||||
allOptions.DownlevelIteration = ParseTristate(value)
|
||||
case "erasableSyntaxOnly":
|
||||
allOptions.ErasableSyntaxOnly = ParseTristate(value)
|
||||
case "emitDeclarationOnly":
|
||||
allOptions.EmitDeclarationOnly = ParseTristate(value)
|
||||
case "extendedDiagnostics":
|
||||
allOptions.ExtendedDiagnostics = ParseTristate(value)
|
||||
case "emitDecoratorMetadata":
|
||||
allOptions.EmitDecoratorMetadata = ParseTristate(value)
|
||||
case "emitBOM":
|
||||
allOptions.EmitBOM = ParseTristate(value)
|
||||
case "esModuleInterop":
|
||||
allOptions.ESModuleInterop = ParseTristate(value)
|
||||
case "exactOptionalPropertyTypes":
|
||||
allOptions.ExactOptionalPropertyTypes = ParseTristate(value)
|
||||
case "explainFiles":
|
||||
allOptions.ExplainFiles = ParseTristate(value)
|
||||
case "experimentalDecorators":
|
||||
allOptions.ExperimentalDecorators = ParseTristate(value)
|
||||
case "forceConsistentCasingInFileNames":
|
||||
allOptions.ForceConsistentCasingInFileNames = ParseTristate(value)
|
||||
case "generateCpuProfile":
|
||||
allOptions.GenerateCpuProfile = ParseString(value)
|
||||
case "generateTrace":
|
||||
allOptions.GenerateTrace = ParseString(value)
|
||||
case "isolatedModules":
|
||||
allOptions.IsolatedModules = ParseTristate(value)
|
||||
case "ignoreConfig":
|
||||
allOptions.IgnoreConfig = ParseTristate(value)
|
||||
case "ignoreDeprecations":
|
||||
allOptions.IgnoreDeprecations = ParseString(value)
|
||||
case "importHelpers":
|
||||
allOptions.ImportHelpers = ParseTristate(value)
|
||||
case "incremental":
|
||||
allOptions.Incremental = ParseTristate(value)
|
||||
case "init":
|
||||
allOptions.Init = ParseTristate(value)
|
||||
case "inlineSourceMap":
|
||||
allOptions.InlineSourceMap = ParseTristate(value)
|
||||
case "inlineSources":
|
||||
allOptions.InlineSources = ParseTristate(value)
|
||||
case "isolatedDeclarations":
|
||||
allOptions.IsolatedDeclarations = ParseTristate(value)
|
||||
case "jsx":
|
||||
allOptions.Jsx = floatOrInt32ToFlag[core.JsxEmit](value)
|
||||
case "jsxFactory":
|
||||
allOptions.JsxFactory = ParseString(value)
|
||||
case "jsxFragmentFactory":
|
||||
allOptions.JsxFragmentFactory = ParseString(value)
|
||||
case "jsxImportSource":
|
||||
allOptions.JsxImportSource = ParseString(value)
|
||||
case "lib":
|
||||
if _, ok := value.([]string); ok {
|
||||
allOptions.Lib = value.([]string)
|
||||
} else {
|
||||
allOptions.Lib = ParseStringArray(value)
|
||||
}
|
||||
case "libReplacement":
|
||||
allOptions.LibReplacement = ParseTristate(value)
|
||||
case "listEmittedFiles":
|
||||
allOptions.ListEmittedFiles = ParseTristate(value)
|
||||
case "listFiles":
|
||||
allOptions.ListFiles = ParseTristate(value)
|
||||
case "listFilesOnly":
|
||||
allOptions.ListFilesOnly = ParseTristate(value)
|
||||
case "locale":
|
||||
allOptions.Locale = ParseString(value)
|
||||
case "mapRoot":
|
||||
allOptions.MapRoot = ParseString(value)
|
||||
case "module":
|
||||
allOptions.Module = floatOrInt32ToFlag[core.ModuleKind](value)
|
||||
case "moduleDetectionKind":
|
||||
allOptions.ModuleDetection = floatOrInt32ToFlag[core.ModuleDetectionKind](value)
|
||||
case "moduleResolution":
|
||||
allOptions.ModuleResolution = floatOrInt32ToFlag[core.ModuleResolutionKind](value)
|
||||
case "moduleSuffixes":
|
||||
allOptions.ModuleSuffixes = ParseStringArray(value)
|
||||
case "moduleDetection":
|
||||
allOptions.ModuleDetection = floatOrInt32ToFlag[core.ModuleDetectionKind](value)
|
||||
case "noCheck":
|
||||
allOptions.NoCheck = ParseTristate(value)
|
||||
case "noFallthroughCasesInSwitch":
|
||||
allOptions.NoFallthroughCasesInSwitch = ParseTristate(value)
|
||||
case "noEmitForJsFiles":
|
||||
allOptions.NoEmitForJsFiles = ParseTristate(value)
|
||||
case "noErrorTruncation":
|
||||
allOptions.NoErrorTruncation = ParseTristate(value)
|
||||
case "noImplicitAny":
|
||||
allOptions.NoImplicitAny = ParseTristate(value)
|
||||
case "noImplicitThis":
|
||||
allOptions.NoImplicitThis = ParseTristate(value)
|
||||
case "noLib":
|
||||
allOptions.NoLib = ParseTristate(value)
|
||||
case "noPropertyAccessFromIndexSignature":
|
||||
allOptions.NoPropertyAccessFromIndexSignature = ParseTristate(value)
|
||||
case "noUncheckedIndexedAccess":
|
||||
allOptions.NoUncheckedIndexedAccess = ParseTristate(value)
|
||||
case "noEmitHelpers":
|
||||
allOptions.NoEmitHelpers = ParseTristate(value)
|
||||
case "noEmitOnError":
|
||||
allOptions.NoEmitOnError = ParseTristate(value)
|
||||
case "noImplicitReturns":
|
||||
allOptions.NoImplicitReturns = ParseTristate(value)
|
||||
case "noUnusedLocals":
|
||||
allOptions.NoUnusedLocals = ParseTristate(value)
|
||||
case "noUnusedParameters":
|
||||
allOptions.NoUnusedParameters = ParseTristate(value)
|
||||
case "noImplicitOverride":
|
||||
allOptions.NoImplicitOverride = ParseTristate(value)
|
||||
case "noUncheckedSideEffectImports":
|
||||
allOptions.NoUncheckedSideEffectImports = ParseTristate(value)
|
||||
case "outFile":
|
||||
allOptions.OutFile = ParseString(value)
|
||||
case "noResolve":
|
||||
allOptions.NoResolve = ParseTristate(value)
|
||||
case "paths":
|
||||
allOptions.Paths = parseStringMap(value)
|
||||
case "preserveWatchOutput":
|
||||
allOptions.PreserveWatchOutput = ParseTristate(value)
|
||||
case "preserveConstEnums":
|
||||
allOptions.PreserveConstEnums = ParseTristate(value)
|
||||
case "preserveSymlinks":
|
||||
allOptions.PreserveSymlinks = ParseTristate(value)
|
||||
case "project":
|
||||
allOptions.Project = ParseString(value)
|
||||
case "pretty":
|
||||
allOptions.Pretty = ParseTristate(value)
|
||||
case "resolveJsonModule":
|
||||
allOptions.ResolveJsonModule = ParseTristate(value)
|
||||
case "resolvePackageJsonExports":
|
||||
allOptions.ResolvePackageJsonExports = ParseTristate(value)
|
||||
case "resolvePackageJsonImports":
|
||||
allOptions.ResolvePackageJsonImports = ParseTristate(value)
|
||||
case "reactNamespace":
|
||||
allOptions.ReactNamespace = ParseString(value)
|
||||
case "rewriteRelativeImportExtensions":
|
||||
allOptions.RewriteRelativeImportExtensions = ParseTristate(value)
|
||||
case "rootDir":
|
||||
allOptions.RootDir = ParseString(value)
|
||||
case "rootDirs":
|
||||
allOptions.RootDirs = ParseStringArray(value)
|
||||
case "removeComments":
|
||||
allOptions.RemoveComments = ParseTristate(value)
|
||||
case "stableTypeOrdering":
|
||||
allOptions.StableTypeOrdering = ParseTristate(value)
|
||||
case "strict":
|
||||
allOptions.Strict = ParseTristate(value)
|
||||
case "strictBindCallApply":
|
||||
allOptions.StrictBindCallApply = ParseTristate(value)
|
||||
case "strictBuiltinIteratorReturn":
|
||||
allOptions.StrictBuiltinIteratorReturn = ParseTristate(value)
|
||||
case "strictFunctionTypes":
|
||||
allOptions.StrictFunctionTypes = ParseTristate(value)
|
||||
case "strictNullChecks":
|
||||
allOptions.StrictNullChecks = ParseTristate(value)
|
||||
case "strictPropertyInitialization":
|
||||
allOptions.StrictPropertyInitialization = ParseTristate(value)
|
||||
case "skipDefaultLibCheck":
|
||||
allOptions.SkipDefaultLibCheck = ParseTristate(value)
|
||||
case "sourceMap":
|
||||
allOptions.SourceMap = ParseTristate(value)
|
||||
case "sourceRoot":
|
||||
allOptions.SourceRoot = ParseString(value)
|
||||
case "stripInternal":
|
||||
allOptions.StripInternal = ParseTristate(value)
|
||||
case "suppressOutputPathCheck":
|
||||
allOptions.SuppressOutputPathCheck = ParseTristate(value)
|
||||
case "target":
|
||||
allOptions.Target = floatOrInt32ToFlag[core.ScriptTarget](value)
|
||||
case "traceResolution":
|
||||
allOptions.TraceResolution = ParseTristate(value)
|
||||
case "tsBuildInfoFile":
|
||||
allOptions.TsBuildInfoFile = ParseString(value)
|
||||
case "typeRoots":
|
||||
allOptions.TypeRoots = ParseStringArray(value)
|
||||
case "types":
|
||||
allOptions.Types = ParseStringArray(value)
|
||||
case "useDefineForClassFields":
|
||||
allOptions.UseDefineForClassFields = ParseTristate(value)
|
||||
case "useUnknownInCatchVariables":
|
||||
allOptions.UseUnknownInCatchVariables = ParseTristate(value)
|
||||
case "verbatimModuleSyntax":
|
||||
allOptions.VerbatimModuleSyntax = ParseTristate(value)
|
||||
case "version":
|
||||
allOptions.Version = ParseTristate(value)
|
||||
case "help":
|
||||
allOptions.Help = ParseTristate(value)
|
||||
case "all":
|
||||
allOptions.All = ParseTristate(value)
|
||||
case "maxNodeModuleJsDepth":
|
||||
allOptions.MaxNodeModuleJsDepth = parseNumber(value)
|
||||
case "skipLibCheck":
|
||||
allOptions.SkipLibCheck = ParseTristate(value)
|
||||
case "noEmit":
|
||||
allOptions.NoEmit = ParseTristate(value)
|
||||
case "showConfig":
|
||||
allOptions.ShowConfig = ParseTristate(value)
|
||||
case "configFilePath":
|
||||
allOptions.ConfigFilePath = ParseString(value)
|
||||
case "noDtsResolution":
|
||||
allOptions.NoDtsResolution = ParseTristate(value)
|
||||
case "pathsBasePath":
|
||||
allOptions.PathsBasePath = ParseString(value)
|
||||
case "outDir":
|
||||
allOptions.OutDir = ParseString(value)
|
||||
case "newLine":
|
||||
allOptions.NewLine = floatOrInt32ToFlag[core.NewLineKind](value)
|
||||
case "watch":
|
||||
allOptions.Watch = ParseTristate(value)
|
||||
case "pprofDir":
|
||||
allOptions.PprofDir = ParseString(value)
|
||||
case "singleThreaded":
|
||||
allOptions.SingleThreaded = ParseTristate(value)
|
||||
case "quiet":
|
||||
allOptions.Quiet = ParseTristate(value)
|
||||
case "checkers":
|
||||
allOptions.Checkers = parseNumber(value)
|
||||
default:
|
||||
// different than any key above
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func floatOrInt32ToFlag[T ~int32](value any) T {
|
||||
if v, ok := value.(T); ok {
|
||||
return v
|
||||
}
|
||||
return T(value.(float64))
|
||||
}
|
||||
|
||||
func ParseWatchOptions(key string, value any, allOptions *core.WatchOptions) []*ast.Diagnostic {
|
||||
if allOptions == nil {
|
||||
return nil
|
||||
}
|
||||
switch key {
|
||||
case "watchInterval":
|
||||
allOptions.Interval = parseNumber(value)
|
||||
case "watchFile":
|
||||
if value != nil {
|
||||
allOptions.FileKind = value.(core.WatchFileKind)
|
||||
}
|
||||
case "watchDirectory":
|
||||
if value != nil {
|
||||
allOptions.DirectoryKind = value.(core.WatchDirectoryKind)
|
||||
}
|
||||
case "fallbackPolling":
|
||||
if value != nil {
|
||||
allOptions.FallbackPolling = value.(core.PollingKind)
|
||||
}
|
||||
case "synchronousWatchDirectory":
|
||||
allOptions.SyncWatchDir = ParseTristate(value)
|
||||
case "excludeDirectories":
|
||||
allOptions.ExcludeDir = ParseStringArray(value)
|
||||
case "excludeFiles":
|
||||
allOptions.ExcludeFiles = ParseStringArray(value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ParseTypeAcquisition(key string, value any, allOptions *core.TypeAcquisition) []*ast.Diagnostic {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
if allOptions == nil {
|
||||
return nil
|
||||
}
|
||||
switch key {
|
||||
case "enable":
|
||||
allOptions.Enable = ParseTristate(value)
|
||||
case "include":
|
||||
allOptions.Include = ParseStringArray(value)
|
||||
case "exclude":
|
||||
allOptions.Exclude = ParseStringArray(value)
|
||||
case "disableFilenameBasedTypeAcquisition":
|
||||
allOptions.DisableFilenameBasedTypeAcquisition = ParseTristate(value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ParseBuildOptions(key string, value any, allOptions *core.BuildOptions) []*ast.Diagnostic {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
if allOptions == nil {
|
||||
return nil
|
||||
}
|
||||
option := BuildNameMap.Get(key)
|
||||
if option != nil {
|
||||
key = option.Name
|
||||
}
|
||||
switch key {
|
||||
case "clean":
|
||||
allOptions.Clean = ParseTristate(value)
|
||||
case "dry":
|
||||
allOptions.Dry = ParseTristate(value)
|
||||
case "force":
|
||||
allOptions.Force = ParseTristate(value)
|
||||
case "builders":
|
||||
allOptions.Builders = parseNumber(value)
|
||||
case "stopBuildOnErrors":
|
||||
allOptions.StopBuildOnErrors = ParseTristate(value)
|
||||
case "verbose":
|
||||
allOptions.Verbose = ParseTristate(value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// mergeCompilerOptions merges the source compiler options into the target compiler options
|
||||
// with optional awareness of explicitly set null values in the raw JSON.
|
||||
// Fields in the source options will overwrite the corresponding fields in the target options,
|
||||
// including when they are explicitly set to null in the raw configuration (if rawSource is provided).
|
||||
func mergeCompilerOptions(targetOptions, sourceOptions *core.CompilerOptions, rawSource any) *core.CompilerOptions {
|
||||
if sourceOptions == nil {
|
||||
return targetOptions
|
||||
}
|
||||
|
||||
// Collect explicitly null field names from raw JSON
|
||||
var explicitNullFields collections.Set[string]
|
||||
if rawSource != nil {
|
||||
if rawMap, ok := rawSource.(*collections.OrderedMap[string, any]); ok && rawMap != nil {
|
||||
// Options are nested under "compilerOptions" in both tsconfig.json and wrapped command line options
|
||||
if compilerOptionsRaw, exists := rawMap.Get("compilerOptions"); exists {
|
||||
if compilerOptionsMap, ok := compilerOptionsRaw.(*collections.OrderedMap[string, any]); ok {
|
||||
for key, value := range compilerOptionsMap.Entries() {
|
||||
if value == nil {
|
||||
explicitNullFields.Add(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Do the merge, handling explicit nulls during the normal merge
|
||||
targetValue := reflect.ValueOf(targetOptions).Elem()
|
||||
sourceValue := reflect.ValueOf(sourceOptions).Elem()
|
||||
targetType := targetValue.Type()
|
||||
|
||||
for i := range targetValue.NumField() {
|
||||
targetField := targetValue.Field(i)
|
||||
sourceField := sourceValue.Field(i)
|
||||
|
||||
// Get the JSON field name for this struct field and check if it's explicitly null
|
||||
if jsonTag := targetType.Field(i).Tag.Get("json"); jsonTag != "" {
|
||||
if jsonFieldName, _, _ := strings.Cut(jsonTag, ","); jsonFieldName != "" && explicitNullFields.Has(jsonFieldName) {
|
||||
targetField.SetZero()
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Normal merge behavior: copy non-zero fields
|
||||
if !sourceField.IsZero() {
|
||||
targetField.Set(sourceField)
|
||||
}
|
||||
}
|
||||
|
||||
return targetOptions
|
||||
}
|
||||
|
||||
func convertToOptionsWithAbsolutePaths(optionsBase *collections.OrderedMap[string, any], optionMap CommandLineOptionNameMap, cwd string) *collections.OrderedMap[string, any] {
|
||||
// !!! convert to options with absolute paths was previously done with `CompilerOptions` object, but for ease of implementation, we do it pre-conversion.
|
||||
// !!! Revisit this choice if/when refactoring when conversion is done in tsconfig parsing
|
||||
if optionsBase == nil {
|
||||
return nil
|
||||
}
|
||||
for o, v := range optionsBase.Entries() {
|
||||
result, ok := ConvertOptionToAbsolutePath(o, v, optionMap, cwd)
|
||||
if ok {
|
||||
optionsBase.Set(o, result)
|
||||
}
|
||||
}
|
||||
return optionsBase
|
||||
}
|
||||
|
||||
func ConvertOptionToAbsolutePath(o string, v any, optionMap CommandLineOptionNameMap, cwd string) (any, bool) {
|
||||
option := optionMap.Get(o)
|
||||
if option == nil {
|
||||
return nil, false
|
||||
}
|
||||
if option.Kind == "list" {
|
||||
if option.Elements().IsFilePath {
|
||||
if arr, ok := v.([]string); ok {
|
||||
return core.Map(arr, func(item string) string {
|
||||
return tspath.GetNormalizedAbsolutePath(item, cwd)
|
||||
}), true
|
||||
}
|
||||
if arr, ok := v.([]any); ok {
|
||||
return core.Map(arr, func(item any) any {
|
||||
if s, isStr := item.(string); isStr {
|
||||
return tspath.GetNormalizedAbsolutePath(s, cwd)
|
||||
}
|
||||
return item
|
||||
}), true
|
||||
}
|
||||
}
|
||||
} else if option.IsFilePath {
|
||||
if value, ok := v.(string); ok {
|
||||
return tspath.GetNormalizedAbsolutePath(value, cwd), true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
36
tools/tsgo/internal/tsoptions/parsinghelpers_test.go
Normal file
36
tools/tsgo/internal/tsoptions/parsinghelpers_test.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package tsoptions
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
)
|
||||
|
||||
func TestParseCompilerOptionNoMissingFields(t *testing.T) {
|
||||
t.Parallel()
|
||||
var missingKeys []string
|
||||
for _, field := range reflect.VisibleFields(reflect.TypeFor[core.CompilerOptions]()) {
|
||||
if !field.IsExported() {
|
||||
continue
|
||||
}
|
||||
|
||||
keyName := field.Name
|
||||
// use the JSON key from the tag, if present
|
||||
// e.g. `json:"dog[,anythingelse]"` --> dog
|
||||
if jsonTag, ok := field.Tag.Lookup("json"); ok {
|
||||
keyName = strings.SplitN(jsonTag, ",", 2)[0]
|
||||
}
|
||||
val := reflect.Zero(field.Type).Interface()
|
||||
co := core.CompilerOptions{}
|
||||
found := parseCompilerOptions(keyName, val, &co)
|
||||
if !found {
|
||||
missingKeys = append(missingKeys, keyName)
|
||||
}
|
||||
}
|
||||
if len(missingKeys) > 0 {
|
||||
t.Errorf("The following keys are missing entries in the ParseCompilerOptions"+
|
||||
" switch statement:\n%v", missingKeys)
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
14
tools/tsgo/internal/tsoptions/testmain_test.go
Normal file
14
tools/tsgo/internal/tsoptions/testmain_test.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package tsoptions_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/testutil/baseline"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
core.ApplyDebugStackLimit()
|
||||
defer baseline.Track()()
|
||||
m.Run()
|
||||
}
|
||||
1842
tools/tsgo/internal/tsoptions/tsconfigparsing.go
Normal file
1842
tools/tsgo/internal/tsoptions/tsconfigparsing.go
Normal file
File diff suppressed because it is too large
Load Diff
1568
tools/tsgo/internal/tsoptions/tsconfigparsing_test.go
Normal file
1568
tools/tsgo/internal/tsoptions/tsconfigparsing_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
||||
package tsoptionstest
|
||||
|
||||
import (
|
||||
"github.com/microsoft/typescript-go/internal/tsoptions"
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func GetParsedCommandLine(t assert.TestingT, jsonText string, files map[string]string, currentDirectory string, useCaseSensitiveFileNames bool) *tsoptions.ParsedCommandLine {
|
||||
host := NewVFSParseConfigHost(files, currentDirectory, useCaseSensitiveFileNames)
|
||||
configFileName := tspath.CombinePaths(currentDirectory, "tsconfig.json")
|
||||
tsconfigSourceFile := tsoptions.NewTsconfigSourceFileFromFilePath(configFileName, tspath.ToPath(configFileName, currentDirectory, useCaseSensitiveFileNames), jsonText)
|
||||
return tsoptions.ParseJsonSourceFileConfigFileContent(tsconfigSourceFile, host, currentDirectory, nil, nil, configFileName, nil, nil, nil)
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package tsoptionstest
|
||||
|
||||
import (
|
||||
"github.com/microsoft/typescript-go/internal/tsoptions"
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
"github.com/microsoft/typescript-go/internal/vfs"
|
||||
"github.com/microsoft/typescript-go/internal/vfs/vfstest"
|
||||
)
|
||||
|
||||
func fixRoot(path string) string {
|
||||
rootLength := tspath.GetRootLength(path)
|
||||
if rootLength == 0 {
|
||||
return path
|
||||
}
|
||||
if len(path) == rootLength {
|
||||
return "."
|
||||
}
|
||||
return path[rootLength:]
|
||||
}
|
||||
|
||||
type VfsParseConfigHost struct {
|
||||
Vfs vfs.FS
|
||||
CurrentDirectory string
|
||||
}
|
||||
|
||||
var _ tsoptions.ParseConfigHost = (*VfsParseConfigHost)(nil)
|
||||
|
||||
func (h *VfsParseConfigHost) FS() vfs.FS {
|
||||
return h.Vfs
|
||||
}
|
||||
|
||||
func (h *VfsParseConfigHost) GetCurrentDirectory() string {
|
||||
return h.CurrentDirectory
|
||||
}
|
||||
|
||||
func NewVFSParseConfigHost(files map[string]string, currentDirectory string, useCaseSensitiveFileNames bool) *VfsParseConfigHost {
|
||||
return &VfsParseConfigHost{
|
||||
Vfs: vfstest.FromMap(files, useCaseSensitiveFileNames),
|
||||
CurrentDirectory: currentDirectory,
|
||||
}
|
||||
}
|
||||
134
tools/tsgo/internal/tsoptions/wildcarddirectories.go
Normal file
134
tools/tsgo/internal/tsoptions/wildcarddirectories.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package tsoptions
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
"github.com/microsoft/typescript-go/internal/vfs/vfsmatch"
|
||||
)
|
||||
|
||||
func getWildcardDirectories(include []string, exclude []string, comparePathsOptions tspath.ComparePathsOptions) map[string]bool {
|
||||
// We watch a directory recursively if it contains a wildcard anywhere in a directory segment
|
||||
// of the pattern:
|
||||
//
|
||||
// /a/b/**/d - Watch /a/b recursively to catch changes to any d in any subfolder recursively
|
||||
// /a/b/*/d - Watch /a/b recursively to catch any d in any immediate subfolder, even if a new subfolder is added
|
||||
// /a/b - Watch /a/b recursively to catch changes to anything in any recursive subfoler
|
||||
//
|
||||
// We watch a directory without recursion if it contains a wildcard in the file segment of
|
||||
// the pattern:
|
||||
//
|
||||
// /a/b/* - Watch /a/b directly to catch any new file
|
||||
// /a/b/a?z - Watch /a/b directly to catch any new file matching a?z
|
||||
|
||||
if len(include) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
excludeMatcher := vfsmatch.NewSpecMatcher(exclude, comparePathsOptions.CurrentDirectory, vfsmatch.UsageExclude, comparePathsOptions.UseCaseSensitiveFileNames)
|
||||
|
||||
wildcardDirectories := make(map[string]bool)
|
||||
wildCardKeyToPath := make(map[string]string)
|
||||
|
||||
var recursiveKeys []string
|
||||
|
||||
for _, file := range include {
|
||||
spec := tspath.NormalizePath(tspath.CombinePaths(comparePathsOptions.CurrentDirectory, file))
|
||||
if excludeMatcher != nil && excludeMatcher.MatchString(spec) {
|
||||
continue
|
||||
}
|
||||
|
||||
match := getWildcardDirectoryFromSpec(spec, comparePathsOptions.UseCaseSensitiveFileNames)
|
||||
if match != nil {
|
||||
key := match.Key
|
||||
path := match.Path
|
||||
recursive := match.Recursive
|
||||
|
||||
existingPath, existsPath := wildCardKeyToPath[key]
|
||||
var existingRecursive bool
|
||||
|
||||
if existsPath {
|
||||
existingRecursive = wildcardDirectories[existingPath]
|
||||
}
|
||||
|
||||
if !existsPath || (!existingRecursive && recursive) {
|
||||
pathToUse := path
|
||||
if existsPath {
|
||||
pathToUse = existingPath
|
||||
}
|
||||
wildcardDirectories[pathToUse] = recursive
|
||||
|
||||
if !existsPath {
|
||||
wildCardKeyToPath[key] = path
|
||||
}
|
||||
|
||||
if recursive {
|
||||
recursiveKeys = append(recursiveKeys, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any subpaths under an existing recursively watched directory
|
||||
for path := range wildcardDirectories {
|
||||
for _, recursiveKey := range recursiveKeys {
|
||||
key := toCanonicalKey(path, comparePathsOptions.UseCaseSensitiveFileNames)
|
||||
if key != recursiveKey && tspath.ContainsPath(recursiveKey, key, comparePathsOptions) {
|
||||
delete(wildcardDirectories, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return wildcardDirectories
|
||||
}
|
||||
|
||||
func toCanonicalKey(path string, useCaseSensitiveFileNames bool) string {
|
||||
if useCaseSensitiveFileNames {
|
||||
return path
|
||||
}
|
||||
return strings.ToLower(path)
|
||||
}
|
||||
|
||||
// wildcardDirectoryMatch represents the result of a wildcard directory match
|
||||
type wildcardDirectoryMatch struct {
|
||||
Key string
|
||||
Path string
|
||||
Recursive bool
|
||||
}
|
||||
|
||||
func getWildcardDirectoryFromSpec(spec string, useCaseSensitiveFileNames bool) *wildcardDirectoryMatch {
|
||||
// Find the first occurrence of a wildcard character
|
||||
firstWildcard := strings.IndexAny(spec, "*?")
|
||||
if firstWildcard != -1 {
|
||||
// Find the last directory separator before the wildcard
|
||||
lastSepBeforeWildcard := strings.LastIndexByte(spec[:firstWildcard], tspath.DirectorySeparator)
|
||||
if lastSepBeforeWildcard != -1 {
|
||||
path := spec[:lastSepBeforeWildcard]
|
||||
lastDirectorySeparatorIndex := strings.LastIndexByte(spec, tspath.DirectorySeparator)
|
||||
|
||||
// Determine if this should be watched recursively:
|
||||
// recursive if the wildcard appears in a directory segment (not just the final file segment)
|
||||
recursive := firstWildcard < lastDirectorySeparatorIndex
|
||||
|
||||
return &wildcardDirectoryMatch{
|
||||
Key: toCanonicalKey(path, useCaseSensitiveFileNames),
|
||||
Path: path,
|
||||
Recursive: recursive,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if lastSepIndex := strings.LastIndexByte(spec, tspath.DirectorySeparator); lastSepIndex != -1 {
|
||||
lastSegment := spec[lastSepIndex+1:]
|
||||
if vfsmatch.IsImplicitGlob(lastSegment) {
|
||||
path := tspath.RemoveTrailingDirectorySeparator(spec)
|
||||
return &wildcardDirectoryMatch{
|
||||
Key: toCanonicalKey(path, useCaseSensitiveFileNames),
|
||||
Path: path,
|
||||
Recursive: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
85
tools/tsgo/internal/tsoptions/wildcarddirectories_test.go
Normal file
85
tools/tsgo/internal/tsoptions/wildcarddirectories_test.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package tsoptions
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestGetWildcardDirectories_DotPrefixedIncludeWithDotDirExclude(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// https://github.com/microsoft/typescript-go/issues/3733
|
||||
// "./"-prefixed include specs must be fully normalized before being tested
|
||||
// against exclude patterns; otherwise the leftover literal "." path segment
|
||||
// matches dot-directory excludes like "**/.*/", silently dropping every
|
||||
// wildcard directory (and with them, root file watching for the config).
|
||||
result := getWildcardDirectories(
|
||||
[]string{"./app/**/*.ts", "./app/**/*.tsx"},
|
||||
[]string{"**/node_modules", "**/.*/", "./build"},
|
||||
tspath.ComparePathsOptions{
|
||||
CurrentDirectory: "/home/projects/monorepo/apps/web",
|
||||
UseCaseSensitiveFileNames: true,
|
||||
},
|
||||
)
|
||||
assert.DeepEqual(t, result, map[string]bool{"/home/projects/monorepo/apps/web/app": true})
|
||||
}
|
||||
|
||||
func TestGetWildcardDirectories_NonASCIICharacters(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
include []string
|
||||
exclude []string
|
||||
currentDirectory string
|
||||
useCaseSensitiveFileNames bool
|
||||
}{
|
||||
{
|
||||
name: "Norwegian character æ in path",
|
||||
include: []string{"src/**/*.test.ts", "src/**/*.stories.ts", "src/**/*.mdx"},
|
||||
exclude: []string{"node_modules"},
|
||||
currentDirectory: "C:/Users/TobiasLægreid/dev/app/frontend/packages/react",
|
||||
useCaseSensitiveFileNames: false,
|
||||
},
|
||||
{
|
||||
name: "Japanese characters in path",
|
||||
include: []string{"src/**/*.ts"},
|
||||
exclude: []string{"テスト"},
|
||||
currentDirectory: "/Users/ユーザー/プロジェクト",
|
||||
useCaseSensitiveFileNames: true,
|
||||
},
|
||||
{
|
||||
name: "Chinese characters in path",
|
||||
include: []string{"源代码/**/*.js"},
|
||||
exclude: []string{"节点模块"},
|
||||
currentDirectory: "/home/用户/项目",
|
||||
useCaseSensitiveFileNames: true,
|
||||
},
|
||||
{
|
||||
name: "Various Unicode characters",
|
||||
include: []string{"src/**/*.ts"},
|
||||
exclude: []string{"node_modules"},
|
||||
currentDirectory: "/Users/Müller/café/naïve/résumé",
|
||||
useCaseSensitiveFileNames: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
comparePathsOptions := tspath.ComparePathsOptions{
|
||||
CurrentDirectory: tt.currentDirectory,
|
||||
UseCaseSensitiveFileNames: tt.useCaseSensitiveFileNames,
|
||||
}
|
||||
|
||||
result := getWildcardDirectories(tt.include, tt.exclude, comparePathsOptions)
|
||||
|
||||
if result == nil {
|
||||
t.Fatalf("expected non-nil result")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user