Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion internal/compiler/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,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/execute/build/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ 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)
commandLine, _ := tsoptions.GetParsedCommandLineOfConfigFilePath(fileName, path, h.orchestrator.opts.Command.CompilerOptions, nil /*optionsRaw*/, h, &h.extendedConfigCache)
configTime := h.orchestrator.opts.Sys.Now().Sub(configStart)
h.configTimes.Store(path, configTime)
return commandLine
Expand Down
7 changes: 6 additions & 1 deletion internal/execute/tsc.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"strings"

"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/collections"
"github.com/microsoft/typescript-go/internal/compiler"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/diagnostics"
Expand Down Expand Up @@ -173,7 +174,11 @@ 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 {
commandLineRaw = raw
}
configParseResult, errors := tsoptions.GetParsedCommandLineOfConfigFileWithRaw(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/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
43 changes: 43 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,48 @@ 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
parsedConfig, errors := tsoptions.GetParsedCommandLineOfConfigFileWithRaw(
"/project/tsconfig.json",
cmdLine.CompilerOptions(),
cmdLine.Raw.(*collections.OrderedMap[string, any]),
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
10 changes: 9 additions & 1 deletion internal/tsoptions/parsinghelpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,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 {
// For tsconfig.json, options are nested under "compilerOptions"
if compilerOptionsRaw, exists := rawMap.Get("compilerOptions"); exists {
if compilerOptionsMap, ok := compilerOptionsRaw.(*collections.OrderedMap[string, any]); ok {
for key, value := range compilerOptionsMap.Entries() {
Expand All @@ -553,6 +554,13 @@ func mergeCompilerOptions(targetOptions, sourceOptions *core.CompilerOptions, ra
}
}
}
} else {
// For command line options, the map IS the options directly
for key, value := range rawMap.Entries() {
if value == nil {
explicitNullFields.Add(key)
}
}
}
}
}
Expand Down
55 changes: 49 additions & 6 deletions internal/tsoptions/tsconfigparsing.go
Original file line number Diff line number Diff line change
Expand Up @@ -691,7 +691,24 @@ func ParseJsonSourceFileConfigFileContent(
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, nil /*existingOptionsRaw*/, configFileName, resolutionStack, extraFileExtensions, extendedConfigCache)
// tracing?.pop();
return result
}

func ParseJsonSourceFileConfigFileContentWithRaw(
sourceFile *TsConfigSourceFile,
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, existingOptionsRaw, configFileName, resolutionStack, extraFileExtensions, extendedConfigCache)
// tracing?.pop();
return result
}
Expand Down Expand Up @@ -829,7 +846,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 @@ -994,11 +1011,11 @@ func parseConfig(
var result *parsedTsconfig
errors = append(errors, ast.NewCompilerDiagnostic(diagnostics.Circularity_detected_while_resolving_configuration_Colon_0))
if json.Size() == 0 {
result = &parsedTsconfig{raw: json}
result = &parsedTsconfig{raw: json, options: &core.CompilerOptions{}}
} else {
rawResult, err := convertToObject(sourceFile.SourceFile)
errors = append(errors, err...)
result = &parsedTsconfig{raw: rawResult}
result = &parsedTsconfig{raw: rawResult, options: &core.CompilerOptions{}}
}
return result, errors
}
Expand Down Expand Up @@ -1127,6 +1144,7 @@ func parseJsonConfigFileContentWorker(
host ParseConfigHost,
basePath string,
existingOptions *core.CompilerOptions,
existingOptionsRaw *collections.OrderedMap[string, any],
configFileName string,
resolutionStack []tspath.Path,
extraFileExtensions []FileExtensionInfo,
Expand All @@ -1144,7 +1162,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 @@ -1743,13 +1761,25 @@ func GetParsedCommandLineOfConfigFile(
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, nil /*optionsRaw*/, sys, extendedConfigCache)
}

func GetParsedCommandLineOfConfigFileWithRaw(
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, 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 @@ -1763,6 +1793,19 @@ func GetParsedCommandLineOfConfigFilePath(
tsConfigSourceFile := NewTsconfigSourceFileFromFilePath(configFileName, path, configFileText)
// tsConfigSourceFile.resolvedPath = tsConfigSourceFile.FileName()
// tsConfigSourceFile.originalFileName = tsConfigSourceFile.FileName()
if optionsRaw != nil {
return ParseJsonSourceFileConfigFileContentWithRaw(
tsConfigSourceFile,
sys,
tspath.GetDirectoryPath(configFileName),
options,
optionsRaw,
configFileName,
nil,
nil,
extendedConfigCache,
), nil
}
return ParseJsonSourceFileConfigFileContent(
tsConfigSourceFile,
sys,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,69 +41,10 @@ interface Symbol {
readonly [Symbol.toStringTag]: string;
}
declare const console: { log(msg: any): void; };
//// [/home/src/workspaces/project/src/main.d.ts] *new*
export declare const x = 10;

//// [/home/src/workspaces/project/src/main.js] *new*
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.x = void 0;
exports.x = 10;

//// [/home/src/workspaces/project/tsconfig.tsbuildinfo] *new*
{"version":"FakeTSVersion","root":[2],"fileNames":["lib.d.ts","./src/main.ts"],"fileInfos":[{"version":"8859c12c614ce56ba9a18e58384a198f-/// <reference no-default-lib=\"true\"/>\ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array<T> { length: number; [n: number]: T; }\ninterface ReadonlyArray<T> {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"28e8748a7acd58f4f59388926e914f86-export const x = 10;","signature":"f9b4154a9a5944099ecf197d4519d083-export declare const x = 10;\n","impliedNodeFormat":1}],"options":{"composite":true,"module":1,"target":1},"latestChangedDtsFile":"./src/main.d.ts"}
//// [/home/src/workspaces/project/tsconfig.tsbuildinfo.readable.baseline.txt] *new*
{
"version": "FakeTSVersion",
"root": [
{
"files": [
"./src/main.ts"
],
"original": 2
}
],
"fileNames": [
"lib.d.ts",
"./src/main.ts"
],
"fileInfos": [
{
"fileName": "lib.d.ts",
"version": "8859c12c614ce56ba9a18e58384a198f-/// <reference no-default-lib=\"true\"/>\ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array<T> { length: number; [n: number]: T; }\ninterface ReadonlyArray<T> {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };",
"signature": "8859c12c614ce56ba9a18e58384a198f-/// <reference no-default-lib=\"true\"/>\ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array<T> { length: number; [n: number]: T; }\ninterface ReadonlyArray<T> {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };",
"affectsGlobalScope": true,
"impliedNodeFormat": "CommonJS",
"original": {
"version": "8859c12c614ce56ba9a18e58384a198f-/// <reference no-default-lib=\"true\"/>\ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array<T> { length: number; [n: number]: T; }\ninterface ReadonlyArray<T> {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };",
"affectsGlobalScope": true,
"impliedNodeFormat": 1
}
},
{
"fileName": "./src/main.ts",
"version": "28e8748a7acd58f4f59388926e914f86-export const x = 10;",
"signature": "f9b4154a9a5944099ecf197d4519d083-export declare const x = 10;\n",
"impliedNodeFormat": "CommonJS",
"original": {
"version": "28e8748a7acd58f4f59388926e914f86-export const x = 10;",
"signature": "f9b4154a9a5944099ecf197d4519d083-export declare const x = 10;\n",
"impliedNodeFormat": 1
}
}
],
"options": {
"composite": true,
"module": 1,
"target": 1
},
"latestChangedDtsFile": "./src/main.d.ts",
"size": 1123
}

tsconfig.json::
SemanticDiagnostics::
*refresh* /home/src/tslibs/TS/Lib/lib.d.ts
*refresh* /home/src/workspaces/project/src/main.ts
Signatures::
(stored at emit) /home/src/workspaces/project/src/main.ts