Skip to content

Commit 2e80702

Browse files
Copilotjakebailey
andauthored
Make tsconfig option name lookup case-sensitive with "Did you mean" suggestions (#3589)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>
1 parent f7ec859 commit 2e80702

6 files changed

Lines changed: 208 additions & 10 deletions

File tree

internal/tsoptions/errors.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,18 @@ func extraKeyDiagnostics(s string) *diagnostics.Message {
103103
return nil
104104
}
105105
}
106+
107+
func extraKeyDidYouMeanDiagnostics(s string) *diagnostics.Message {
108+
switch s {
109+
case "compilerOptions":
110+
return diagnostics.Unknown_compiler_option_0_Did_you_mean_1
111+
case "watchOptions":
112+
return diagnostics.Unknown_watch_option_0_Did_you_mean_1
113+
case "typeAcquisition":
114+
return diagnostics.Unknown_type_acquisition_option_0_Did_you_mean_1
115+
case "buildOptions":
116+
return diagnostics.Unknown_build_option_0_Did_you_mean_1
117+
default:
118+
return nil
119+
}
120+
}

internal/tsoptions/parsinghelpers.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ func parseJsonToStringKey(json any) *collections.OrderedMap[string, any] {
122122
type optionParser interface {
123123
ParseOption(key string, value any) []*ast.Diagnostic
124124
UnknownOptionDiagnostic() *diagnostics.Message
125+
UnknownDidYouMeanDiagnostic() *diagnostics.Message
125126
}
126127

127128
type compilerOptionsParser struct {
@@ -136,6 +137,10 @@ func (o *compilerOptionsParser) UnknownOptionDiagnostic() *diagnostics.Message {
136137
return extraKeyDiagnostics("compilerOptions")
137138
}
138139

140+
func (o *compilerOptionsParser) UnknownDidYouMeanDiagnostic() *diagnostics.Message {
141+
return extraKeyDidYouMeanDiagnostics("compilerOptions")
142+
}
143+
139144
type watchOptionsParser struct {
140145
*core.WatchOptions
141146
}
@@ -148,6 +153,10 @@ func (o *watchOptionsParser) UnknownOptionDiagnostic() *diagnostics.Message {
148153
return extraKeyDiagnostics("watchOptions")
149154
}
150155

156+
func (o *watchOptionsParser) UnknownDidYouMeanDiagnostic() *diagnostics.Message {
157+
return extraKeyDidYouMeanDiagnostics("watchOptions")
158+
}
159+
151160
type typeAcquisitionParser struct {
152161
*core.TypeAcquisition
153162
}
@@ -160,6 +169,10 @@ func (o *typeAcquisitionParser) UnknownOptionDiagnostic() *diagnostics.Message {
160169
return extraKeyDiagnostics("typeAcquisition")
161170
}
162171

172+
func (o *typeAcquisitionParser) UnknownDidYouMeanDiagnostic() *diagnostics.Message {
173+
return extraKeyDidYouMeanDiagnostics("typeAcquisition")
174+
}
175+
163176
type buildOptionsParser struct {
164177
*core.BuildOptions
165178
}
@@ -172,6 +185,10 @@ func (o *buildOptionsParser) UnknownOptionDiagnostic() *diagnostics.Message {
172185
return extraKeyDiagnostics("buildOptions")
173186
}
174187

188+
func (o *buildOptionsParser) UnknownDidYouMeanDiagnostic() *diagnostics.Message {
189+
return extraKeyDidYouMeanDiagnostics("buildOptions")
190+
}
191+
175192
func ParseCompilerOptions(key string, value any, allOptions *core.CompilerOptions) []*ast.Diagnostic {
176193
if value == nil {
177194
return nil

internal/tsoptions/tsconfigparsing.go

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -214,15 +214,25 @@ func parseOwnConfigOfJsonSourceFile(
214214
} else if keyText != "" && extraKeyDiagnostics(parentOption.Name) != nil {
215215
unknownNameDiag := extraKeyDiagnostics(parentOption.Name)
216216
if parentOption.ElementOptions != nil {
217-
// !!! TODO: support suggestion
218-
propertySetErrors = append(propertySetErrors, createUnknownOptionError(
219-
keyText,
220-
unknownNameDiag,
221-
"", /*unknownOptionErrorText*/
222-
propertyAssignment.Name(),
223-
sourceFile,
224-
nil, /*alternateMode*/
225-
))
217+
possibleOption := parentOption.ElementOptions.Get(keyText)
218+
if possibleOption != nil && possibleOption.Name != keyText {
219+
propertySetErrors = append(propertySetErrors, CreateDiagnosticForNodeInSourceFileOrCompilerDiagnostic(
220+
sourceFile,
221+
propertyAssignment.Name(),
222+
extraKeyDidYouMeanDiagnostics(parentOption.Name),
223+
keyText,
224+
possibleOption.Name,
225+
))
226+
} else {
227+
propertySetErrors = append(propertySetErrors, createUnknownOptionError(
228+
keyText,
229+
unknownNameDiag,
230+
"", /*unknownOptionErrorText*/
231+
propertyAssignment.Name(),
232+
sourceFile,
233+
nil, /*alternateMode*/
234+
))
235+
}
226236
} else {
227237
// errors = append(errors, ast.NewCompilerDiagnostic(diagnostics.Unknown_compiler_option_0_Did_you_mean_1, keyText, core.FindKey(parentOption.ElementOptions, keyText)))
228238
}
@@ -611,8 +621,12 @@ func convertOptionsFromJson[O optionParser](optionsNameMap CommandLineOptionName
611621
var errors []*ast.Diagnostic
612622
for key, value := range jsonMap.Entries() {
613623
opt := optionsNameMap.Get(key)
624+
if opt != nil && opt.Name != key {
625+
// Case-insensitive match found but exact case doesn't match - provide "did you mean" suggestion
626+
errors = append(errors, CreateDiagnosticForNodeInSourceFileOrCompilerDiagnostic(nil, nil, result.UnknownDidYouMeanDiagnostic(), key, opt.Name))
627+
continue
628+
}
614629
if opt == nil {
615-
// !!! TODO?: support suggestion
616630
errors = append(errors, createUnknownOptionError(key, result.UnknownOptionDiagnostic(), "", nil, nil, nil))
617631
continue
618632
}
@@ -746,6 +760,9 @@ func convertObjectLiteralExpressionToJson(
746760
var option *CommandLineOption = nil
747761
if keyText != "" && objectOption != nil && objectOption.ElementOptions != nil {
748762
option = objectOption.ElementOptions.Get(keyText)
763+
if option != nil && option.Name != keyText {
764+
option = nil
765+
}
749766
}
750767
value, err := convertPropertyValueToJson(sourceFile, element.AsPropertyAssignment().Initializer, option, returnValue, jsonConversionNotifier)
751768
errors = append(errors, err...)

internal/tsoptions/tsconfigparsing_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,29 @@ var parseJsonConfigFileTests = []parseJsonConfigTestCase{
498498
allFileList: map[string]string{"/app.ts": ""},
499499
}},
500500
},
501+
{
502+
title: "reports errors for incorrectly cased option names",
503+
noSubmoduleBaseline: true,
504+
input: []testConfig{{
505+
jsonText: `{
506+
"compilerOptions": {
507+
"sourcemap": true,
508+
"declarationmap": true,
509+
"nouncheckedindexedaccess": true,
510+
"exactoptionalpropertytypes": true,
511+
"verbatimmodulesyntax": true,
512+
"isolatedmodules": true,
513+
"nouncheckedsideeffectimports": true,
514+
"moduledetection": "force",
515+
"skiplibcheck": true,
516+
"checkjs": true
517+
}
518+
}`,
519+
configFileName: "tsconfig.json",
520+
basePath: "/",
521+
allFileList: map[string]string{"/app.ts": ""},
522+
}},
523+
},
501524
{
502525
title: "handles empty types array",
503526
noSubmoduleBaseline: true,
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
Fs::
2+
//// [/app.ts]
3+
4+
5+
//// [/tsconfig.json]
6+
{
7+
"compilerOptions": {
8+
"sourcemap": true,
9+
"declarationmap": true,
10+
"nouncheckedindexedaccess": true,
11+
"exactoptionalpropertytypes": true,
12+
"verbatimmodulesyntax": true,
13+
"isolatedmodules": true,
14+
"nouncheckedsideeffectimports": true,
15+
"moduledetection": "force",
16+
"skiplibcheck": true,
17+
"checkjs": true
18+
}
19+
}
20+
21+
22+
configFileName:: tsconfig.json
23+
CompilerOptions::
24+
{
25+
"configFilePath": "/tsconfig.json"
26+
}
27+
28+
TypeAcquisition::
29+
{}
30+
31+
FileNames::
32+
/app.ts
33+
Errors::
34+
error TS5025: Unknown compiler option 'sourcemap'. Did you mean 'sourceMap'?
35+
error TS5025: Unknown compiler option 'declarationmap'. Did you mean 'declarationMap'?
36+
error TS5025: Unknown compiler option 'nouncheckedindexedaccess'. Did you mean 'noUncheckedIndexedAccess'?
37+
error TS5025: Unknown compiler option 'exactoptionalpropertytypes'. Did you mean 'exactOptionalPropertyTypes'?
38+
error TS5025: Unknown compiler option 'verbatimmodulesyntax'. Did you mean 'verbatimModuleSyntax'?
39+
error TS5025: Unknown compiler option 'isolatedmodules'. Did you mean 'isolatedModules'?
40+
error TS5025: Unknown compiler option 'nouncheckedsideeffectimports'. Did you mean 'noUncheckedSideEffectImports'?
41+
error TS5025: Unknown compiler option 'moduledetection'. Did you mean 'moduleDetection'?
42+
error TS5025: Unknown compiler option 'skiplibcheck'. Did you mean 'skipLibCheck'?
43+
error TS5025: Unknown compiler option 'checkjs'. Did you mean 'checkJs'?
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
Fs::
2+
//// [/app.ts]
3+
4+
5+
//// [/tsconfig.json]
6+
{
7+
"compilerOptions": {
8+
"sourcemap": true,
9+
"declarationmap": true,
10+
"nouncheckedindexedaccess": true,
11+
"exactoptionalpropertytypes": true,
12+
"verbatimmodulesyntax": true,
13+
"isolatedmodules": true,
14+
"nouncheckedsideeffectimports": true,
15+
"moduledetection": "force",
16+
"skiplibcheck": true,
17+
"checkjs": true
18+
}
19+
}
20+
21+
22+
configFileName:: tsconfig.json
23+
CompilerOptions::
24+
{
25+
"configFilePath": "/tsconfig.json"
26+
}
27+
28+
TypeAcquisition::
29+
{}
30+
31+
FileNames::
32+
/app.ts
33+
Errors::
34+
tsconfig.json:3:5 - error TS5025: Unknown compiler option 'sourcemap'. Did you mean 'sourceMap'?
35+
36+
3 "sourcemap": true,
37+
   ~~~~~~~~~~~
38+
39+
tsconfig.json:4:5 - error TS5025: Unknown compiler option 'declarationmap'. Did you mean 'declarationMap'?
40+
41+
4 "declarationmap": true,
42+
   ~~~~~~~~~~~~~~~~
43+
44+
tsconfig.json:5:5 - error TS5025: Unknown compiler option 'nouncheckedindexedaccess'. Did you mean 'noUncheckedIndexedAccess'?
45+
46+
5 "nouncheckedindexedaccess": true,
47+
   ~~~~~~~~~~~~~~~~~~~~~~~~~~
48+
49+
tsconfig.json:6:5 - error TS5025: Unknown compiler option 'exactoptionalpropertytypes'. Did you mean 'exactOptionalPropertyTypes'?
50+
51+
6 "exactoptionalpropertytypes": true,
52+
   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
53+
54+
tsconfig.json:7:5 - error TS5025: Unknown compiler option 'verbatimmodulesyntax'. Did you mean 'verbatimModuleSyntax'?
55+
56+
7 "verbatimmodulesyntax": true,
57+
   ~~~~~~~~~~~~~~~~~~~~~~
58+
59+
tsconfig.json:8:5 - error TS5025: Unknown compiler option 'isolatedmodules'. Did you mean 'isolatedModules'?
60+
61+
8 "isolatedmodules": true,
62+
   ~~~~~~~~~~~~~~~~~
63+
64+
tsconfig.json:9:5 - error TS5025: Unknown compiler option 'nouncheckedsideeffectimports'. Did you mean 'noUncheckedSideEffectImports'?
65+
66+
9 "nouncheckedsideeffectimports": true,
67+
   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
68+
69+
tsconfig.json:10:5 - error TS5025: Unknown compiler option 'moduledetection'. Did you mean 'moduleDetection'?
70+
71+
10 "moduledetection": "force",
72+
   ~~~~~~~~~~~~~~~~~
73+
74+
tsconfig.json:11:5 - error TS5025: Unknown compiler option 'skiplibcheck'. Did you mean 'skipLibCheck'?
75+
76+
11 "skiplibcheck": true,
77+
   ~~~~~~~~~~~~~~
78+
79+
tsconfig.json:12:5 - error TS5025: Unknown compiler option 'checkjs'. Did you mean 'checkJs'?
80+
81+
12 "checkjs": true
82+
   ~~~~~~~~~
83+

0 commit comments

Comments
 (0)