Skip to content
1 change: 1 addition & 0 deletions internal/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ func (api *API) ParseConfigFile(configFileName string) (*ConfigFileResponse, err
api.session,
configDir,
nil, /*existingOptions*/
nil, /*existingOptionsRaw*/
configFileName,
nil, /*resolutionStack*/
nil, /*extraFileExtensions*/
Expand Down
6 changes: 3 additions & 3 deletions internal/checker/checker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ foo.bar;`
cd := "/"
host := compiler.NewCompilerHost(cd, fs, bundled.LibPath(), nil, nil)

parsed, errors := tsoptions.GetParsedCommandLineOfConfigFile("/tsconfig.json", &core.CompilerOptions{}, host, nil)
parsed, errors := tsoptions.GetParsedCommandLineOfConfigFile("/tsconfig.json", &core.CompilerOptions{}, nil, host, nil)
assert.Equal(t, len(errors), 0, "Expected no errors in parsed command line")

p := compiler.NewProgram(compiler.ProgramOptions{
Expand Down Expand Up @@ -71,7 +71,7 @@ func TestCheckSrcCompiler(t *testing.T) {
rootPath := tspath.CombinePaths(tspath.NormalizeSlashes(repo.TypeScriptSubmodulePath), "src", "compiler")

host := compiler.NewCompilerHost(rootPath, fs, bundled.LibPath(), nil, nil)
parsed, errors := tsoptions.GetParsedCommandLineOfConfigFile(tspath.CombinePaths(rootPath, "tsconfig.json"), &core.CompilerOptions{}, host, nil)
parsed, errors := tsoptions.GetParsedCommandLineOfConfigFile(tspath.CombinePaths(rootPath, "tsconfig.json"), &core.CompilerOptions{}, nil, host, nil)
assert.Equal(t, len(errors), 0, "Expected no errors in parsed command line")
p := compiler.NewProgram(compiler.ProgramOptions{
Config: parsed,
Expand All @@ -88,7 +88,7 @@ func BenchmarkNewChecker(b *testing.B) {
rootPath := tspath.CombinePaths(tspath.NormalizeSlashes(repo.TypeScriptSubmodulePath), "src", "compiler")

host := compiler.NewCompilerHost(rootPath, fs, bundled.LibPath(), nil, nil)
parsed, errors := tsoptions.GetParsedCommandLineOfConfigFile(tspath.CombinePaths(rootPath, "tsconfig.json"), &core.CompilerOptions{}, host, nil)
parsed, errors := tsoptions.GetParsedCommandLineOfConfigFile(tspath.CombinePaths(rootPath, "tsconfig.json"), &core.CompilerOptions{}, nil, host, nil)
assert.Equal(b, len(errors), 0, "Expected no errors in parsed command line")
p := compiler.NewProgram(compiler.ProgramOptions{
Config: parsed,
Expand Down
2 changes: 1 addition & 1 deletion internal/compiler/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,6 @@ func (h *compilerHost) GetSourceFile(opts ast.SourceFileParseOptions) *ast.Sourc
}

func (h *compilerHost) GetResolvedProjectReference(fileName string, path tspath.Path) *tsoptions.ParsedCommandLine {
commandLine, _ := tsoptions.GetParsedCommandLineOfConfigFilePath(fileName, path, nil, h, h.extendedConfigCache)
commandLine, _ := tsoptions.GetParsedCommandLineOfConfigFilePath(fileName, path, nil, nil /*optionsRaw*/, h, h.extendedConfigCache)
return commandLine
}
2 changes: 1 addition & 1 deletion internal/compiler/program_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ func BenchmarkNewProgram(b *testing.B) {

host := compiler.NewCompilerHost(rootPath, fs, bundled.LibPath(), nil, nil)

parsed, errors := tsoptions.GetParsedCommandLineOfConfigFile(tspath.CombinePaths(rootPath, "tsconfig.json"), nil, host, nil)
parsed, errors := tsoptions.GetParsedCommandLineOfConfigFile(tspath.CombinePaths(rootPath, "tsconfig.json"), nil, nil, host, nil)
assert.Equal(b, len(errors), 0, "Expected no errors in parsed command line")

opts := compiler.ProgramOptions{
Expand Down
9 changes: 8 additions & 1 deletion internal/execute/build/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,14 @@ func (h *host) GetSourceFile(opts ast.SourceFileParseOptions) *ast.SourceFile {
func (h *host) GetResolvedProjectReference(fileName string, path tspath.Path) *tsoptions.ParsedCommandLine {
return h.resolvedReferences.loadOrStoreNew(path, func(path tspath.Path) *tsoptions.ParsedCommandLine {
configStart := h.orchestrator.opts.Sys.Now()
commandLine, _ := tsoptions.GetParsedCommandLineOfConfigFilePath(fileName, path, h.orchestrator.opts.Command.CompilerOptions, h, &h.extendedConfigCache)
// Wrap command line options in "compilerOptions" key to match tsconfig.json structure
var commandLineRaw *collections.OrderedMap[string, any]
if raw, ok := h.orchestrator.opts.Command.Raw.(*collections.OrderedMap[string, any]); ok {
wrapped := &collections.OrderedMap[string, any]{}
wrapped.Set("compilerOptions", raw)
commandLineRaw = wrapped
}
commandLine, _ := tsoptions.GetParsedCommandLineOfConfigFilePath(fileName, path, h.orchestrator.opts.Command.CompilerOptions, commandLineRaw, h, &h.extendedConfigCache)
configTime := h.orchestrator.opts.Sys.Now().Sub(configStart)
h.configTimes.Store(path, configTime)
return commandLine
Expand Down
9 changes: 8 additions & 1 deletion internal/execute/tsc.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,14 @@ func tscCompilation(sys tsc.System, commandLine *tsoptions.ParsedCommandLine, te
var compileTimes tsc.CompileTimes
if configFileName != "" {
configStart := sys.Now()
configParseResult, errors := tsoptions.GetParsedCommandLineOfConfigFile(configFileName, compilerOptionsFromCommandLine, sys, extendedConfigCache)
var commandLineRaw *collections.OrderedMap[string, any]
if raw, ok := commandLine.Raw.(*collections.OrderedMap[string, any]); ok {
// Wrap command line options in a "compilerOptions" key to match tsconfig.json structure
wrapped := &collections.OrderedMap[string, any]{}
wrapped.Set("compilerOptions", raw)
commandLineRaw = wrapped
}
configParseResult, errors := tsoptions.GetParsedCommandLineOfConfigFile(configFileName, compilerOptionsFromCommandLine, commandLineRaw, sys, extendedConfigCache)
compileTimes.ConfigTime = sys.Now().Sub(configStart)
if len(errors) != 0 {
// these are unrecoverable errors--exit to report them as diagnostics
Expand Down
2 changes: 1 addition & 1 deletion internal/execute/tsctests/tscbuild_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1545,7 +1545,7 @@ func TestBuildOutputPaths(t *testing.T) {
t.Run("GetOutputFileNames/"+s.subScenario, func(t *testing.T) {
t.Parallel()
sys := newTestSys(input, false)
config, _ := tsoptions.GetParsedCommandLineOfConfigFile("/home/src/workspaces/project/tsconfig.json", &core.CompilerOptions{}, sys, nil)
config, _ := tsoptions.GetParsedCommandLineOfConfigFile("/home/src/workspaces/project/tsconfig.json", &core.CompilerOptions{}, nil, sys, nil)
assert.DeepEqual(t, slices.Collect(config.GetOutputFileNames()), s.expectedDtsNames)
})
}
Expand Down
2 changes: 1 addition & 1 deletion internal/execute/watcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ func (w *Watcher) hasErrorsInTsConfig() bool {
extendedConfigCache := &tsc.ExtendedConfigCache{}
if w.configFileName != "" {
// !!! need to check that this merges compileroptions correctly. This differs from non-watch, since we allow overriding of previous options
configParseResult, errors := tsoptions.GetParsedCommandLineOfConfigFile(w.configFileName, w.compilerOptionsFromCommandLine, w.sys, extendedConfigCache)
configParseResult, errors := tsoptions.GetParsedCommandLineOfConfigFile(w.configFileName, w.compilerOptionsFromCommandLine, nil, w.sys, extendedConfigCache)
if len(errors) > 0 {
for _, e := range errors {
w.reportDiagnostic(e)
Expand Down
2 changes: 1 addition & 1 deletion internal/project/configfileregistrybuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func (c *configFileRegistryBuilder) reloadIfNeeded(entry *configFileEntry, fileN
entry.commandLine = entry.commandLine.ReloadFileNamesOfParsedCommandLine(c.fs.fs)
case PendingReloadFull:
logger.Log("Loading config file: " + fileName)
entry.commandLine, _ = tsoptions.GetParsedCommandLineOfConfigFilePath(fileName, path, nil, c, c)
entry.commandLine, _ = tsoptions.GetParsedCommandLineOfConfigFilePath(fileName, path, nil, nil /*optionsRaw*/, c, c)
c.updateExtendingConfigs(path, entry.commandLine, entry.commandLine)
c.updateRootFilesWatch(fileName, entry)
logger.Log("Finished loading config file")
Expand Down
1 change: 1 addition & 0 deletions internal/testrunner/test_case_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ func makeUnitsFromTest(code string, fileName string) testCaseContent {
parseConfigHost,
configDir,
nil, /*existingOptions*/
nil, /*existingOptionsRaw*/
configFileName,
nil, /*resolutionStack*/
nil, /*extraFileExtensions*/
Expand Down
1 change: 1 addition & 0 deletions internal/tsoptions/commandlineparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ func ParseBuildCommandLine(
WatchOptions: convertMapToOptions(parser.options, &watchOptionsParser{&core.WatchOptions{}}).WatchOptions,
Projects: parser.fileNames,
Errors: parser.errors,
Raw: parser.options,

comparePathsOptions: tspath.ComparePathsOptions{
UseCaseSensitiveFileNames: host.FS().UseCaseSensitiveFileNames(),
Expand Down
46 changes: 46 additions & 0 deletions internal/tsoptions/commandlineparser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/go-json-experiment/json"
"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"
Expand Down Expand Up @@ -86,6 +87,51 @@ func TestCommandLineParseResult(t *testing.T) {
}
}

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)
Expand Down
1 change: 1 addition & 0 deletions internal/tsoptions/parsedbuildcommandline.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type ParsedBuildCommandLine struct {
WatchOptions *core.WatchOptions `json:"watchOptions"`
Projects []string `json:"projects"`
Errors []*ast.Diagnostic `json:"errors"`
Raw any `json:"raw"`

comparePathsOptions tspath.ComparePathsOptions

Expand Down
3 changes: 2 additions & 1 deletion internal/tsoptions/parsinghelpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,8 @@ func mergeCompilerOptions(targetOptions, sourceOptions *core.CompilerOptions, ra
// 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 {
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() {
Expand Down
13 changes: 9 additions & 4 deletions internal/tsoptions/tsconfigparsing.go
Original file line number Diff line number Diff line change
Expand Up @@ -695,13 +695,14 @@ func ParseJsonSourceFileConfigFileContent(
host ParseConfigHost,
basePath string,
existingOptions *core.CompilerOptions,
existingOptionsRaw *collections.OrderedMap[string, any],
configFileName string,
resolutionStack []tspath.Path,
extraFileExtensions []FileExtensionInfo,
extendedConfigCache ExtendedConfigCache,
) *ParsedCommandLine {
// tracing?.push(tracing.Phase.Parse, "parseJsonSourceFileConfigFileContent", { path: sourceFile.fileName });
result := parseJsonConfigFileContentWorker(nil /*json*/, sourceFile, host, basePath, existingOptions, configFileName, resolutionStack, extraFileExtensions, extendedConfigCache)
result := parseJsonConfigFileContentWorker(nil /*json*/, sourceFile, host, basePath, existingOptions, existingOptionsRaw, configFileName, resolutionStack, extraFileExtensions, extendedConfigCache)
// tracing?.pop();
return result
}
Expand Down Expand Up @@ -839,7 +840,7 @@ func convertPropertyValueToJson(sourceFile *ast.SourceFile, valueExpression *ast
// host: Instance of ParseConfigHost used to enumerate files in folder.
// basePath: A root directory to resolve relative path entries in the config file to. e.g. outDir
func ParseJsonConfigFileContent(json any, host ParseConfigHost, basePath string, existingOptions *core.CompilerOptions, configFileName string, resolutionStack []tspath.Path, extraFileExtensions []FileExtensionInfo, extendedConfigCache ExtendedConfigCache) *ParsedCommandLine {
result := parseJsonConfigFileContentWorker(parseJsonToStringKey(json), nil /*sourceFile*/, host, basePath, existingOptions, configFileName, resolutionStack, extraFileExtensions, extendedConfigCache)
result := parseJsonConfigFileContentWorker(parseJsonToStringKey(json), nil /*sourceFile*/, host, basePath, existingOptions, nil /*existingOptionsRaw*/, configFileName, resolutionStack, extraFileExtensions, extendedConfigCache)
return result
}

Expand Down Expand Up @@ -1137,6 +1138,7 @@ func parseJsonConfigFileContentWorker(
host ParseConfigHost,
basePath string,
existingOptions *core.CompilerOptions,
existingOptionsRaw *collections.OrderedMap[string, any],
configFileName string,
resolutionStack []tspath.Path,
extraFileExtensions []FileExtensionInfo,
Expand All @@ -1154,7 +1156,7 @@ func parseJsonConfigFileContentWorker(
var errors []*ast.Diagnostic
resolutionStackString := []string{}
parsedConfig, errors := parseConfig(json, sourceFile, host, basePath, configFileName, resolutionStackString, extendedConfigCache)
mergeCompilerOptions(parsedConfig.options, existingOptions, nil)
mergeCompilerOptions(parsedConfig.options, existingOptions, existingOptionsRaw)
handleOptionConfigDirTemplateSubstitution(parsedConfig.options, basePathForFileNames)
rawConfig := parseJsonToStringKey(parsedConfig.raw)
if configFileName != "" && parsedConfig.options != nil {
Expand Down Expand Up @@ -1749,17 +1751,19 @@ func GetSupportedExtensionsWithJsonIfResolveJsonModule(compilerOptions *core.Com
func GetParsedCommandLineOfConfigFile(
configFileName string,
options *core.CompilerOptions,
optionsRaw *collections.OrderedMap[string, any],
sys ParseConfigHost,
extendedConfigCache ExtendedConfigCache,
) (*ParsedCommandLine, []*ast.Diagnostic) {
configFileName = tspath.GetNormalizedAbsolutePath(configFileName, sys.GetCurrentDirectory())
return GetParsedCommandLineOfConfigFilePath(configFileName, tspath.ToPath(configFileName, sys.GetCurrentDirectory(), sys.FS().UseCaseSensitiveFileNames()), options, sys, extendedConfigCache)
return GetParsedCommandLineOfConfigFilePath(configFileName, tspath.ToPath(configFileName, sys.GetCurrentDirectory(), sys.FS().UseCaseSensitiveFileNames()), options, optionsRaw, sys, extendedConfigCache)
}

func GetParsedCommandLineOfConfigFilePath(
configFileName string,
path tspath.Path,
options *core.CompilerOptions,
optionsRaw *collections.OrderedMap[string, any],
sys ParseConfigHost,
extendedConfigCache ExtendedConfigCache,
) (*ParsedCommandLine, []*ast.Diagnostic) {
Expand All @@ -1778,6 +1782,7 @@ func GetParsedCommandLineOfConfigFilePath(
sys,
tspath.GetDirectoryPath(configFileName),
options,
optionsRaw,
configFileName,
nil,
nil,
Expand Down
5 changes: 5 additions & 0 deletions internal/tsoptions/tsconfigparsing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -820,6 +820,7 @@ func getParsedWithJsonSourceFileApi(config testConfig, host tsoptions.ParseConfi
host,
host.GetCurrentDirectory(),
nil,
nil,
configFileName,
/*resolutionStack*/ nil,
/*extraFileExtensions*/ nil,
Expand Down Expand Up @@ -1040,6 +1041,7 @@ func TestParseSrcCompiler(t *testing.T) {
host,
host.GetCurrentDirectory(),
nil,
nil,
tsconfigFileName,
/*resolutionStack*/ nil,
/*extraFileExtensions*/ nil,
Expand Down Expand Up @@ -1202,6 +1204,7 @@ func BenchmarkParseSrcCompiler(b *testing.B) {
host,
host.GetCurrentDirectory(),
nil,
nil,
tsconfigFileName,
/*resolutionStack*/ nil,
/*extraFileExtensions*/ nil,
Expand Down Expand Up @@ -1262,6 +1265,7 @@ func TestExtendedConfigErrorsAppearOnCacheHit(t *testing.T) {
host,
host.GetCurrentDirectory(),
nil,
nil,
configFileName,
nil,
nil,
Expand Down Expand Up @@ -1306,6 +1310,7 @@ func TestExtendedConfigErrorsAppearOnCacheHit(t *testing.T) {
host,
host.GetCurrentDirectory(),
nil,
nil,
configFileName,
nil,
nil,
Expand Down
2 changes: 1 addition & 1 deletion internal/tsoptions/tsoptionstest/parsedcommandline.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ func GetParsedCommandLine(t assert.TestingT, jsonText string, files map[string]s
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, configFileName, nil, nil, nil)
return tsoptions.ParseJsonSourceFileConfigFileContent(tsconfigSourceFile, host, currentDirectory, nil, nil, configFileName, nil, nil, nil)
}
Loading