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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 36 additions & 2 deletions tsc/internal/execute/tsctests/tscwatch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,8 +267,7 @@ func TestWatch(t *testing.T) {
commandLineArgs: []string{"--watch"},
edits: []*tscEdit{
{
caption: "remove nested dir",
expectedDiff: "incremental has prior state and does not report no-inputs error",
caption: "remove nested dir",
edit: func(sys *TestSys) {
sys.removeNoError("/home/src/workspaces/project/src/lib/helper.ts")
},
Expand Down Expand Up @@ -600,6 +599,41 @@ func TestWatch(t *testing.T) {
},
}

for _, build := range []bool{false, true} {
for _, initiallyEmpty := range []bool{false, true} {
args := []string{"--watch"}
if build {
args = []string{"--build", "--watch", "--force"}
}
files := FileMap{
"/home/src/workspaces/project/tsconfig.json": fmt.Sprintf(`{
"compilerOptions": { "composite": %t, "outDir": "out" },
"include": ["src/**/*.ts"]
}`, build),
}
const sourcePath = "/home/src/workspaces/project/src/helper.ts"
if !initiallyEmpty {
files[sourcePath] = `export const helper = 1;`
}
addFile := newTscEdit("add first source file", func(sys *TestSys) {
sys.writeFileNoError(sourcePath, `export const helper = 2;`)
})
removeFile := newTscEdit("remove last source file", func(sys *TestSys) {
sys.removeNoError(sourcePath)
})
edits := []*tscEdit{removeFile, addFile}
if initiallyEmpty {
edits = []*tscEdit{addFile, removeFile}
}
testCases = append(testCases, &tscInput{
subScenario: fmt.Sprintf("watch updates no-inputs diagnostics build %t initiallyEmpty %t", build, initiallyEmpty),
files: files,
commandLineArgs: args,
edits: edits,
})
}
}

for _, test := range testCases {
test.run(t, "commandLineWatch")
}
Expand Down
8 changes: 7 additions & 1 deletion tsc/internal/tsoptions/parsedcommandline.go
Original file line number Diff line number Diff line change
Expand Up @@ -510,10 +510,16 @@ func (p *ParsedCommandLine) ReloadFileNamesOfParsedCommandLine(fs vfs.FS) *Parse
p.ContentMapperExtensions(),
)
parsedConfig.FileNames = fileNames
errors := slices.DeleteFunc(slices.Clone(p.Errors), func(diagnostic *ast.Diagnostic) bool {
return diagnostic.Code() == diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.Code()
})
if raw, ok := p.Raw.(*collections.OrderedMap[string, any]); ok && shouldReportNoInputFiles(fileNames, canJsonReportNoInputFiles(raw), nil) {
errors = append(errors, getErrorForNoInputFiles(p.getConfigFileSpecs(), p.ConfigFileName()))
}
Comment on lines +516 to +518
parsedCommandLine := ParsedCommandLine{
ParsedConfig: &parsedConfig,
ConfigFile: p.ConfigFile,
Errors: p.Errors,
Errors: errors,
Raw: p.Raw,
CompileOnSave: p.CompileOnSave,
configFileSpecs: p.configFileSpecs,
Expand Down
38 changes: 38 additions & 0 deletions tsc/internal/tsoptions/parsedcommandline_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import (
"slices"
"testing"

"github.com/microsoft/TypeScript/tsc/internal/ast"
"github.com/microsoft/TypeScript/tsc/internal/core"
"github.com/microsoft/TypeScript/tsc/internal/diagnostics"
"github.com/microsoft/TypeScript/tsc/internal/tsoptions"
"github.com/microsoft/TypeScript/tsc/internal/tsoptions/tsoptionstest"
"github.com/microsoft/TypeScript/tsc/internal/tspath"
Expand All @@ -14,6 +16,42 @@ import (

func TestParsedCommandLine(t *testing.T) {
t.Parallel()
t.Run("ReloadFileNames updates no-input diagnostics", func(t *testing.T) {
t.Parallel()
for _, config := range []string{
`{"include": ["*.ts"]}`,
`{"files": [], "include": ["*.ts"]}`,
`{"references": [], "include": ["*.ts"]}`,
`{"compilerOptions": {"strict": "invalid"}, "include": ["*.ts"]}`,
} {
t.Run(config, func(t *testing.T) {
t.Parallel()
withFiles := map[string]string{"/dev/a.ts": ""}
withoutFiles := map[string]string{}
initial := tsoptionstest.GetParsedCommandLine(t, config, withFiles, "/dev", tspath.CaseSensitive)
originalErrors := slices.Clone(initial.Errors)
empty := initial.ReloadFileNamesOfParsedCommandLine(vfstest.FromMap(withoutFiles, tspath.CaseSensitive))
cleanEmpty := tsoptionstest.GetParsedCommandLine(t, config, withoutFiles, "/dev", tspath.CaseSensitive)
compareDiagnostics := func(actual, expected []*ast.Diagnostic) {
t.Helper()
assert.Equal(t, len(actual), len(expected))
for i := range actual {
assert.Equal(t, ast.CompareDiagnostics(actual[i], expected[i]), 0)
}
}
compareDiagnostics(empty.Errors, cleanEmpty.Errors)
assert.Assert(t, slices.Equal(initial.Errors, originalErrors))
reloadedEmpty := empty.ReloadFileNamesOfParsedCommandLine(vfstest.FromMap(withoutFiles, tspath.CaseSensitive))
compareDiagnostics(reloadedEmpty.Errors, cleanEmpty.Errors)
restored := empty.ReloadFileNamesOfParsedCommandLine(vfstest.FromMap(withFiles, tspath.CaseSensitive))
compareDiagnostics(restored.Errors, initial.Errors)
compareDiagnostics(empty.Errors, cleanEmpty.Errors)
for _, diagnostic := range restored.Errors {
assert.Assert(t, diagnostic.Code() != diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.Code())
}
})
}
})
t.Run("PossiblyMatchesFileName", func(t *testing.T) {
t.Parallel()

Expand Down
22 changes: 13 additions & 9 deletions tsc/internal/tsoptions/tsconfigparsing.go
Original file line number Diff line number Diff line change
Expand Up @@ -1361,15 +1361,7 @@ func parseJsonConfigFileContentWorker(
parsedConfigOptions := parsedConfig.options
fileNames, literalFileNamesLen := getFileNamesFromConfigSpecs(configFileSpecs, basePath, parsedConfigOptions.CompilerOptions, fs, contentMapperExtensions)
if shouldReportNoInputFiles(fileNames, canJsonReportNoInputFiles(rawConfig), resolutionStack) {
includeSpecs := configFileSpecs.includeSpecs
excludeSpecs := configFileSpecs.excludeSpecs
if includeSpecs == nil {
includeSpecs = []string{}
}
if excludeSpecs == nil {
excludeSpecs = []string{}
}
errors = append(errors, ast.NewCompilerDiagnostic(diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, configFileName, core.Must(core.StringifyJson(includeSpecs, "", "")), core.Must(core.StringifyJson(excludeSpecs, "", ""))))
errors = append(errors, getErrorForNoInputFiles(&configFileSpecs, configFileName))
}
return fileNames, literalFileNamesLen
}
Expand Down Expand Up @@ -1442,6 +1434,18 @@ func shouldReportNoInputFiles(fileNames []tspath.RootedFilePath, canJsonReportNo
return len(fileNames) == 0 && canJsonReportNoInputFiles && len(resolutionStack) == 0
}

func getErrorForNoInputFiles(specs *configFileSpecs, configFileName tspath.RootedFilePath) *ast.Diagnostic {
includeSpecs := specs.includeSpecs
excludeSpecs := specs.excludeSpecs
if includeSpecs == nil {
includeSpecs = []string{}
}
if excludeSpecs == nil {
excludeSpecs = []string{}
}
return ast.NewCompilerDiagnostic(diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2, configFileName, core.Must(core.StringifyJson(includeSpecs, "", "")), core.Must(core.StringifyJson(excludeSpecs, "", "")))
}

func validateSpecs(specs any, disallowTrailingRecursion bool, jsonSourceFile *ast.SourceFile, specKey string) ([]tspath.PathPattern, []*ast.Diagnostic) {
createDiagnostic := func(message *diagnostics.Message, spec string) *ast.Diagnostic {
element := GetTsConfigPropArrayElementValue(jsonSourceFile, specKey, spec)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
currentDirectory::/home/src/workspaces/project
useCaseSensitiveFileNames::true
Input::
//// [/home/src/workspaces/project/src/helper.ts] *new*
export const helper = 1;
//// [/home/src/workspaces/project/tsconfig.json] *new*
{
"compilerOptions": { "composite": true, "outDir": "out" },
"include": ["src/**/*.ts"]
}

tsgo --build --watch --force
ExitStatus:: Success
Output::
[HH:MM:SS AM] Starting compilation in watch mode...

[HH:MM:SS AM] Found 0 errors. Watching for file changes.

//// [/home/src/tslibs/TS/Lib/lib.es2026.full.d.ts] *Lib*
/// <reference no-default-lib="true"/>
interface Boolean {}
interface Function {}
interface CallableFunction {}
interface NewableFunction {}
interface IArguments {}
interface Number { toExponential: any; }
interface Object {}
interface RegExp {}
interface String { charAt: any; }
interface Array<T> { length: number; [n: number]: T; }
interface ReadonlyArray<T> {}
interface SymbolConstructor {
(desc?: string | number): symbol;
for(name: string): symbol;
readonly toStringTag: symbol;
}
declare var Symbol: SymbolConstructor;
interface Symbol {
readonly [Symbol.toStringTag]: string;
}
declare const console: { log(msg: any): void; };
//// [/home/src/workspaces/project/out/src/helper.d.ts] *new*
export declare const helper = 1;

//// [/home/src/workspaces/project/out/src/helper.js] *new*
export const helper = 1;

//// [/home/src/workspaces/project/out/tsconfig.tsbuildinfo] *new*
{"version":"FakeTSVersion","root":[2],"fileNames":["lib.es2026.full.d.ts","../src/helper.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":"82911d699c2bb905d385e49d59c85491-export const helper = 1;","signature":"092d764418ff761053476d65b73db943-export declare const helper = 1;\n","impliedNodeFormat":1}],"options":{"composite":true,"outDir":"./"},"latestChangedDtsFile":"./src/helper.d.ts"}
//// [/home/src/workspaces/project/out/tsconfig.tsbuildinfo.readable.baseline.txt] *new*
{
"version": "FakeTSVersion",
"root": [
{
"files": [
"../src/helper.ts"
],
"original": 2
}
],
"fileNames": [
"lib.es2026.full.d.ts",
"../src/helper.ts"
],
"fileInfos": [
{
"fileName": "lib.es2026.full.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/helper.ts",
"version": "82911d699c2bb905d385e49d59c85491-export const helper = 1;",
"signature": "092d764418ff761053476d65b73db943-export declare const helper = 1;\n",
"impliedNodeFormat": "CommonJS",
"original": {
"version": "82911d699c2bb905d385e49d59c85491-export const helper = 1;",
"signature": "092d764418ff761053476d65b73db943-export declare const helper = 1;\n",
"impliedNodeFormat": 1
}
}
],
"options": {
"composite": true,
"outDir": "./"
},
"latestChangedDtsFile": "./src/helper.d.ts",
"size": 1140
}

Watch Registrations::
Directory watches::
/home/src/tslibs/TS/Lib
/home/src/workspaces/project
/home/src/workspaces/project/src (recursive)
tsconfig.json::
SemanticDiagnostics::
*refresh* /home/src/tslibs/TS/Lib/lib.es2026.full.d.ts
*refresh* /home/src/workspaces/project/src/helper.ts
Signatures::
(stored at emit) /home/src/workspaces/project/src/helper.ts


Edit [0]:: remove last source file
//// [/home/src/workspaces/project/src/helper.ts] *deleted*


Output::
[HH:MM:SS AM] File change detected. Starting incremental compilation...

error TS18003: No inputs were found in config file '/home/src/workspaces/project/tsconfig.json'. Specified 'include' paths were '["src/**/*.ts"]' and 'exclude' paths were '["/home/src/workspaces/project/out"]'.
[HH:MM:SS AM] Found 1 error. Watching for file changes.

//// [/home/src/workspaces/project/out/tsconfig.tsbuildinfo] *modified*
{"version":"FakeTSVersion","errors":true,"fileInfos":[],"options":{"composite":true,"outDir":"./"}}
//// [/home/src/workspaces/project/out/tsconfig.tsbuildinfo.readable.baseline.txt] *modified*
{
"version": "FakeTSVersion",
"errors": true,
"fileInfos": [],
"options": {
"composite": true,
"outDir": "./"
},
"size": 99
}

Watch Registrations::
Directory watches::
/home/src/workspaces/project
/home/src/workspaces/project/src (recursive)
tsconfig.json::
SemanticDiagnostics::
Signatures::


Edit [1]:: add first source file
//// [/home/src/workspaces/project/src/helper.ts] *new*
export const helper = 2;


Output::
[HH:MM:SS AM] File change detected. Starting incremental compilation...

[HH:MM:SS AM] Found 0 errors. Watching for file changes.

//// [/home/src/workspaces/project/out/src/helper.d.ts] *modified*
export declare const helper = 2;

//// [/home/src/workspaces/project/out/src/helper.js] *modified*
export const helper = 2;

//// [/home/src/workspaces/project/out/tsconfig.tsbuildinfo] *modified*
{"version":"FakeTSVersion","root":[2],"fileNames":["lib.es2026.full.d.ts","../src/helper.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":"4cfe3c7aa736450b8afd77a4f21c00c8-export const helper = 2;","signature":"4442875ee69a1b282969dfdd30e3eac0-export declare const helper = 2;\n","impliedNodeFormat":1}],"options":{"composite":true,"outDir":"./"},"latestChangedDtsFile":"./src/helper.d.ts"}
//// [/home/src/workspaces/project/out/tsconfig.tsbuildinfo.readable.baseline.txt] *modified*
{
"version": "FakeTSVersion",
"root": [
{
"files": [
"../src/helper.ts"
],
"original": 2
}
],
"fileNames": [
"lib.es2026.full.d.ts",
"../src/helper.ts"
],
"fileInfos": [
{
"fileName": "lib.es2026.full.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/helper.ts",
"version": "4cfe3c7aa736450b8afd77a4f21c00c8-export const helper = 2;",
"signature": "4442875ee69a1b282969dfdd30e3eac0-export declare const helper = 2;\n",
"impliedNodeFormat": "CommonJS",
"original": {
"version": "4cfe3c7aa736450b8afd77a4f21c00c8-export const helper = 2;",
"signature": "4442875ee69a1b282969dfdd30e3eac0-export declare const helper = 2;\n",
"impliedNodeFormat": 1
}
}
],
"options": {
"composite": true,
"outDir": "./"
},
"latestChangedDtsFile": "./src/helper.d.ts",
"size": 1140
}

Watch Registrations::
Directory watches::
/home/src/tslibs/TS/Lib
/home/src/workspaces/project
/home/src/workspaces/project/src (recursive)
tsconfig.json::
SemanticDiagnostics::
*refresh* /home/src/tslibs/TS/Lib/lib.es2026.full.d.ts
*refresh* /home/src/workspaces/project/src/helper.ts
Signatures::
(stored at emit) /home/src/workspaces/project/src/helper.ts
Loading
Loading