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
2 changes: 2 additions & 0 deletions tsc/internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -24362,6 +24362,8 @@ func (c *Checker) computeEnumMemberValue(member *ast.Node, autoValue *jsnum.Numb
text := ast.GetTextOfPropertyName(member.Name())
if isNumericLiteralName(text) && !ast.IsInfinityOrNaNString(text) {
c.error(member.Name(), diagnostics.An_enum_member_cannot_have_a_numeric_name)
} else if ast.IsComputedPropertyName(member.Name()) {
c.addSuggestionDiagnostic(NewDiagnosticForNode(member.Name(), diagnostics.Using_a_string_literal_as_an_enum_member_name_via_a_computed_property_is_deprecated_Use_a_simple_string_literal_instead))
}
}
if member.Initializer() != nil {
Expand Down
13 changes: 13 additions & 0 deletions tsc/internal/diagnostics/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -7393,6 +7393,11 @@
"category": "Suggestion",
"code": 80010
},
"Using a string literal as an enum member name via a computed property is deprecated. Use a simple string literal instead.": {
"category": "Suggestion",
"code": 80011,
"reportsDeprecated": true
},

"Add missing 'super()' call": {
"category": "Message",
Expand Down Expand Up @@ -8383,6 +8388,14 @@
"category": "Message",
"code": 95197
},
"Remove unnecessary computed property name syntax": {
"category": "Message",
"code": 95198
},
"Remove all unnecessary computed property name syntax": {
"category": "Message",
"code": 95199
},

"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
"category": "Error",
Expand Down
12 changes: 12 additions & 0 deletions tsc/internal/diagnostics/diagnostics_generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package fourslash_test

import (
"testing"

"github.com/microsoft/TypeScript/tsc/internal/fourslash"
"github.com/microsoft/TypeScript/tsc/internal/testutil"
)

func TestCodeFixEnumComputedPropertyNameAll(t *testing.T) {
t.Parallel()
const content = `enum CHAR {
/*first*/['\t'] = 0x09,
['\n'] = 0x0A,
[` + "`\\r`" + `] = 0x0D,
Plain = 0x20,
}
const enum Other {
["key"] = 1,
}
enum Invalid {
["a" + "b"] = 1,
[42] = 2,
}`
const expected = `enum CHAR {
"\t" = 0x09,
"\n" = 0x0A,
"\r" = 0x0D,
Plain = 0x20,
}
const enum Other {
"key" = 1,
}
enum Invalid {
["a" + "b"] = 1,
[42] = 2,
}`
for _, sourceFixAll := range []bool{false, true} {
name := "quickfix"
if sourceFixAll {
name = "source.fixAll"
}
t.Run(name, func(t *testing.T) {
t.Parallel()
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
defer done()
f.GoToMarker(t, "first")
if sourceFixAll {
f.VerifySourceFixAll(t, expected)
} else {
f.VerifyCodeFixAll(t, fourslash.VerifyCodeFixAllOptions{
FixID: "convertComputedEnumMemberName",
NewFileContent: expected,
})
}
f.VerifySuggestionDiagnostics(t, nil)
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package fourslash_test

import (
"testing"

"github.com/microsoft/TypeScript/tsc/internal/fourslash"
"github.com/microsoft/TypeScript/tsc/internal/testutil"
)

func TestCodeFixEnumComputedPropertyName(t *testing.T) {
t.Parallel()
for _, test := range []struct {
name string
expression string
literal string
}{
{"string", `'\t'`, `"\t"`},
{"template", "`\\r`", `"\r"`},
{"escapes", `'a"\\b\n'`, `"a\"\\b\n"`},
{"multiline template", "`a\nb`", `"a\nb"`},
{"unicode escape", `'\u0061'`, `"a"`},
{"surrogate", `'\uD800'`, `"\uD800"`},
} {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
content := "enum CHAR {\n [|[" + test.expression + "]|] = 0x09,\n Other = 0x0A,\n}"
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
defer done()
f.GoToRangeStart(t, f.Ranges()[0])
f.VerifyCodeFixAvailableExact(t, []string{"Remove unnecessary computed property name syntax"})
f.VerifyCodeFix(t, fourslash.VerifyCodeFixOptions{
Description: "Remove unnecessary computed property name syntax",
NewFileContent: "enum CHAR {\n " + test.literal + " = 0x09,\n Other = 0x0A,\n}",
ApplyChanges: true,
})
f.VerifyDiagnostics(t, nil)
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package fourslash_test

import (
"testing"

"github.com/microsoft/TypeScript/tsc/internal/fourslash"
"github.com/microsoft/TypeScript/tsc/internal/lsp/lsproto"
"github.com/microsoft/TypeScript/tsc/internal/testutil"
)

func TestEnumComputedPropertyNameDeprecated(t *testing.T) {
t.Parallel()
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `enum CHAR {
[|['\t']|] = 0x09,
[|["\n"]|] = 0x0A,
[|[` + "`\\r`" + `]|] = 0x0D,
'space' = 0x20,
}
enum Names {
A,
"quoted",
[|["key"]|],
[|["Infinity"]|],
[|["NaN"]|],
}
const enum Constants {
[|["constant"]|] = 1,
}
declare enum Ambient {
[|["ambient"]|],
}
const object = { ["key"]: 1 };
class C { ["key"] = 1; }`
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
defer done()
var diagnostics []*lsproto.Diagnostic
for _, r := range f.Ranges() {
diagnostics = append(diagnostics, &lsproto.Diagnostic{
Code: &lsproto.IntegerOrString{Integer: new(int32(80011))},
Message: lsproto.StringOrMarkupContent{String: new("Using a string literal as an enum member name via a computed property is deprecated. Use a simple string literal instead.")},
Tags: &[]lsproto.DiagnosticTag{lsproto.DiagnosticTagDeprecated},
Range: r.LSRange,
})
}
f.VerifyNonSuggestionDiagnostics(t, nil)
f.VerifySuggestionDiagnostics(t, diagnostics)
}
43 changes: 43 additions & 0 deletions tsc/internal/fourslash/tests/enumComputedPropertyNameError_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package fourslash_test

import (
"testing"

"github.com/microsoft/TypeScript/tsc/internal/fourslash"
"github.com/microsoft/TypeScript/tsc/internal/lsp/lsproto"
"github.com/microsoft/TypeScript/tsc/internal/testutil"
)

func TestEnumComputedPropertyNameError(t *testing.T) {
t.Parallel()
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `const key = "dynamic";
enum Invalid {
[|[key]|] = 1,
[|["a" + "b"]|] = 2,
[|[{}]|] = 3,
[|[1]|] = 4,
[|[0x20]|] = 5,
[|["42"]|] = 6,
[|[` + "`43`" + `]|] = 7,
}`
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
defer done()
var diagnostics []*lsproto.Diagnostic
for i, r := range f.Ranges() {
code := int32(1164)
message := "Computed property names are not allowed in enums."
if i >= 3 {
code = 2452
message = "An enum member cannot have a numeric name."
}
diagnostics = append(diagnostics, &lsproto.Diagnostic{
Code: &lsproto.IntegerOrString{Integer: &code},
Message: lsproto.StringOrMarkupContent{String: &message},
Range: r.LSRange,
})
}
f.VerifyNonSuggestionDiagnostics(t, diagnostics)
f.VerifySuggestionDiagnostics(t, nil)
f.VerifyCodeFixNotAvailable(t)
}
1 change: 1 addition & 0 deletions tsc/internal/ls/codeactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ type CombinedCodeActions struct {
var codeFixProviders = []*CodeFixProvider{
ImportFixProvider,
IsolatedDeclarationsFixProvider,
ConvertComputedEnumMemberNameProvider,
FixClassIncorrectlyImplementsInterfaceProvider,
// Add more code fix providers here as they are implemented
}
Expand Down
85 changes: 85 additions & 0 deletions tsc/internal/ls/codeactions_convertcomputedenummembername.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package ls

import (
"context"

"github.com/microsoft/TypeScript/tsc/internal/ast"
"github.com/microsoft/TypeScript/tsc/internal/astnav"
"github.com/microsoft/TypeScript/tsc/internal/diagnostics"
"github.com/microsoft/TypeScript/tsc/internal/locale"
"github.com/microsoft/TypeScript/tsc/internal/ls/change"
)

const convertComputedEnumMemberNameFixID = "convertComputedEnumMemberName"

var convertComputedEnumMemberNameErrorCodes = []int32{
diagnostics.Using_a_string_literal_as_an_enum_member_name_via_a_computed_property_is_deprecated_Use_a_simple_string_literal_instead.Code(),
}

var ConvertComputedEnumMemberNameProvider = &CodeFixProvider{
ErrorCodes: convertComputedEnumMemberNameErrorCodes,
GetCodeActions: getCodeActionsToConvertComputedEnumMemberName,
FixIds: []string{convertComputedEnumMemberNameFixID},
GetAllCodeActions: getAllCodeActionsToConvertComputedEnumMemberName,
}

func getCodeActionsToConvertComputedEnumMemberName(ctx context.Context, fixContext *CodeFixContext) ([]*CodeAction, error) {
name := getComputedEnumMemberName(fixContext.SourceFile, fixContext.Span.Pos())
if name == nil {
return nil, nil
}

tracker := change.NewTracker(ctx, fixContext.Program.Options(), fixContext.LS.FormatOptions(), fixContext.LS.converters)
convertComputedEnumMemberName(tracker, fixContext.SourceFile, name)
changes := getChanges(tracker, nil, fixContext.SourceFile)
if len(changes) == 0 {
return nil, nil
}

loc := locale.FromContext(ctx)
return []*CodeAction{{
Description: diagnostics.Remove_unnecessary_computed_property_name_syntax.Localize(loc),
Changes: changes,
FixID: convertComputedEnumMemberNameFixID,
FixAllDescription: diagnostics.Remove_all_unnecessary_computed_property_name_syntax.Localize(loc),
}}, nil
}

func getAllCodeActionsToConvertComputedEnumMemberName(ctx context.Context, fixContext *CodeFixContext) (*CombinedCodeActions, error) {
tracker := change.NewTracker(ctx, fixContext.Program.Options(), fixContext.LS.FormatOptions(), fixContext.LS.converters)
for _, diagnostic := range getAllDiagnostics(ctx, fixContext.Program, fixContext.SourceFile) {
if !isFixableDiagnostic(diagnostic, convertComputedEnumMemberNameErrorCodes) {
continue
}
file := diagnostic.File()
name := getComputedEnumMemberName(file, diagnostic.Pos())
if name != nil {
convertComputedEnumMemberName(tracker, file, name)
}
}
changes := getChanges(tracker, nil, fixContext.SourceFile)
if len(changes) == 0 {
return nil, nil
}
return &CombinedCodeActions{
Description: diagnostics.Remove_all_unnecessary_computed_property_name_syntax.Localize(locale.FromContext(ctx)),
Changes: changes,
}, nil
}

func getComputedEnumMemberName(file *ast.SourceFile, pos int) *ast.Node {
name := astnav.GetTokenAtPosition(file, pos)
for name != nil && !ast.IsComputedPropertyName(name) {
name = name.Parent
}
if name == nil || !ast.IsEnumMember(name.Parent) || !ast.IsStringLiteralLike(name.Expression()) {
return nil
}
return name
}

func convertComputedEnumMemberName(tracker *change.Tracker, file *ast.SourceFile, name *ast.Node) {
literal := tracker.NewStringLiteral(name.Expression().Text(), ast.TokenFlagsNone)
tracker.AssignCommentRange(literal, name.Expression())
tracker.ReplaceNode(file, name, literal, nil)
}