diff --git a/tsc/internal/checker/exports.go b/tsc/internal/checker/exports.go
index f77488e783142..da326ffcc487f 100644
--- a/tsc/internal/checker/exports.go
+++ b/tsc/internal/checker/exports.go
@@ -152,6 +152,18 @@ func (c *Checker) GetPropertiesOfType(t *Type) []*ast.Symbol {
return c.getPropertiesOfType(t)
}
+func (c *Checker) GetJsxAttributesType(node *ast.Node) *Type {
+ return c.checkExpressionCached(node)
+}
+
+func (c *Checker) GetUnmatchedProperties(source *Type, target *Type, requireOptionalProperties bool, matchDiscriminantProperties bool) []*ast.Symbol {
+ return c.getUnmatchedProperties(source, target, requireOptionalProperties, matchDiscriminantProperties)
+}
+
+func (c *Checker) IsDeeplyNestedType(t *Type, stack []*Type, maxDepth int) bool {
+ return c.isDeeplyNestedType(t, stack, maxDepth)
+}
+
func (c *Checker) GetPropertyOfType(t *Type, name string) *ast.Symbol {
return c.getPropertyOfType(t, name)
}
@@ -172,6 +184,10 @@ func (c *Checker) IsPropertyAccessible(node *ast.Node, isSuper bool, isWrite boo
return c.isPropertyAccessible(node, isSuper, isWrite, containingType, property)
}
+func (c *Checker) IsConstructorAccessible(node *ast.Node, signatures []*Signature) bool {
+ return c.getConstructorAccessibilityError(node, signatures, ast.ModifierFlagsNonPublicAccessibilityModifier) == nil
+}
+
func (c *Checker) GetTypeOfPropertyOfContextualType(t *Type, name string) *Type {
return c.getTypeOfPropertyOfContextualType(t, name)
}
@@ -188,6 +204,10 @@ func (c *Checker) GetSignaturesOfType(t *Type, kind SignatureKind) []*Signature
return c.getSignaturesOfType(t, kind)
}
+func (c *Checker) GetMinArgumentCount(signature *Signature) int {
+ return c.getMinArgumentCount(signature)
+}
+
func (c *Checker) GetDeclaredTypeOfSymbol(symbol *ast.Symbol) *Type {
return c.getDeclaredTypeOfSymbol(symbol)
}
diff --git a/tsc/internal/fourslash/fourslash.go b/tsc/internal/fourslash/fourslash.go
index 7c8fbe3191648..951c6b257b73d 100644
--- a/tsc/internal/fourslash/fourslash.go
+++ b/tsc/internal/fourslash/fourslash.go
@@ -1687,6 +1687,7 @@ type VerifyCodeFixOptions struct {
// VerifyCodeFixAllOptions are the options for VerifyCodeFixAll.
type VerifyCodeFixAllOptions struct {
FixID string
+ Description string
NewFileContent string
}
@@ -1931,7 +1932,8 @@ func (f *FourslashTest) VerifyCodeFixAll(t *testing.T, options VerifyCodeFixAllO
// We look for actions that are NOT single-diagnostic fixes (i.e., have no Diagnostics attached).
var fixAllCandidates []*lsproto.CodeAction
for _, action := range actions {
- if action.Diagnostics == nil || len(*action.Diagnostics) == 0 {
+ if (action.Diagnostics == nil || len(*action.Diagnostics) == 0) &&
+ (options.Description == "" || action.Title == options.Description) {
fixAllCandidates = append(fixAllCandidates, action)
}
}
diff --git a/tsc/internal/fourslash/tests/codeFixAddMissingAttributes10_test.go b/tsc/internal/fourslash/tests/codeFixAddMissingAttributes10_test.go
index a6932a4fd605d..3114624246256 100644
--- a/tsc/internal/fourslash/tests/codeFixAddMissingAttributes10_test.go
+++ b/tsc/internal/fourslash/tests/codeFixAddMissingAttributes10_test.go
@@ -3,7 +3,9 @@ package fourslash_test
import (
"testing"
+ "github.com/microsoft/TypeScript/tsc/internal/diagnostics"
"github.com/microsoft/TypeScript/tsc/internal/fourslash"
+ "github.com/microsoft/TypeScript/tsc/internal/locale"
"github.com/microsoft/TypeScript/tsc/internal/testutil"
)
@@ -20,8 +22,9 @@ const A = (props: { [K in D]: K }) =>
;
const Bar = () =>
- [||]`
- f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
+ `
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content+addMissingAttributesJsxDeclarations)
defer done()
- f.VerifyCodeFixNotAvailable(t, "fixMissingAttributes")
+ f.VerifyNumberOfErrorsInCurrentFile(t, 1)
+ f.VerifyCodeFixNotAvailable(t, diagnostics.Add_missing_attributes.Localize(locale.Default))
}
diff --git a/tsc/internal/fourslash/tests/codeFixAddMissingAttributes11_test.go b/tsc/internal/fourslash/tests/codeFixAddMissingAttributes11_test.go
new file mode 100644
index 0000000000000..339e26c6429c9
--- /dev/null
+++ b/tsc/internal/fourslash/tests/codeFixAddMissingAttributes11_test.go
@@ -0,0 +1,147 @@
+package fourslash_test
+
+import (
+ "strconv"
+ "testing"
+
+ "github.com/microsoft/TypeScript/tsc/internal/diagnostics"
+ "github.com/microsoft/TypeScript/tsc/internal/fourslash"
+ "github.com/microsoft/TypeScript/tsc/internal/locale"
+ "github.com/microsoft/TypeScript/tsc/internal/testutil"
+)
+
+func TestCodeFixAddMissingAttributes11(t *testing.T) {
+ t.Parallel()
+ for i, test := range []struct {
+ content string
+ result string
+ fileResult string
+ filename string
+ }{
+ {
+ content: `// @jsx: preserve
+// @Filename: a.tsx
+interface T { a: number; b?: string }
+const A = (a: T) => null;
+const a = ;`,
+ result: ` a={0}`,
+ },
+ {
+ content: `// @jsx: preserve
+// @Filename: /a.ts
+export enum E { a, b }
+export class C {}
+// @Filename: /b.tsx
+import { E as B, C as D } from "./a";
+const A = (a: { a: B; b: D }) => null;
+const a = ;`,
+ result: ` a={B.a} b={new D}`,
+ filename: "/b.tsx",
+ },
+ {
+ content: `// @jsx: preserve
+// @Filename: a.tsx
+declare namespace JSX {
+ interface Element {}
+ interface ElementChildrenAttribute { children: {} }
+}
+const A = (a: { children: string; b: number }) => null;
+const a = text;`,
+ result: ` b={0}`,
+ },
+ {
+ content: `// @jsx: preserve
+// @Filename: a.tsx
+declare namespace JSX {
+ interface Element {}
+ interface ElementChildrenAttribute { c: {} }
+}
+const A = (a: { c: string; b: number }) => null;
+const a = {"text"};`,
+ result: ` b={0}`,
+ },
+ {
+ content: `// @jsx: preserve
+// @Filename: a.tsx
+declare namespace JSX {
+ interface Element {}
+ interface ElementChildrenAttribute { children: {} }
+}
+const A = (a: { children: string; b: number }) => null;
+const a = {};`,
+ result: ` children={""} b={0}`,
+ },
+ {
+ content: `// @jsx: preserve
+// @Filename: a.tsx
+const A = (a: { a: number; b: string }) => null;
+declare const b: { a?: number };
+const a = ;`,
+ result: ` a={0} b={""}`,
+ },
+ {
+ content: `// @jsx: preserve
+// @Filename: a.tsx
+const A = (a: { a: number; b: string }) => null;
+declare const b: { a: number } | {};
+const a = ;`,
+ result: ` a={0} b={""}`,
+ },
+ {
+ content: `// @jsx: preserve
+// @Filename: a.tsx
+const A = (a: { a: number; b: string }) => null;
+declare const b: { a?: number };
+const a = ;`,
+ result: ` b={""}`,
+ },
+ {
+ content: `// @jsx: preserve
+// @Filename: a.tsx
+const A = (a: { a: number }) => null;
+declare const b: { a?: number };
+const a = ;`,
+ result: ` a={0}`,
+ },
+ {
+ content: `// @jsx: preserve
+// @Filename: a.tsx
+const A = (a: { a: number; b: string }) => null;
+declare const b: { a?: number };
+declare const c: { a: number };
+const a = ;`,
+ result: ` b={""}`,
+ },
+ {
+ content: `// @jsx: preserve
+// @Filename: /a.ts
+export enum E { a, b }
+export class C {}
+// @Filename: /b.tsx
+import type { C as D, E as B } from "./a";
+const A = (a: { a: B; b: D }) => null;
+const a = ;`,
+ fileResult: `import { C as D, E as B } from "./a";
+const A = (a: { a: B; b: D }) => null;
+const a = ;`,
+ filename: "/b.tsx",
+ },
+ } {
+ t.Run(strconv.Itoa(i+1), func(t *testing.T) {
+ t.Parallel()
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, test.content)
+ defer done()
+ if test.filename != "" {
+ f.GoToFile(t, test.filename)
+ }
+ f.VerifyCodeFix(t, fourslash.VerifyCodeFixOptions{
+ Description: diagnostics.Add_missing_attributes.Localize(locale.Default),
+ NewFileContent: test.fileResult,
+ NewRangeContent: test.result,
+ ApplyChanges: true,
+ })
+ f.VerifyNoErrors(t)
+ })
+ }
+}
diff --git a/tsc/internal/fourslash/tests/codeFixAddMissingAttributes12_test.go b/tsc/internal/fourslash/tests/codeFixAddMissingAttributes12_test.go
new file mode 100644
index 0000000000000..eb38c6003826f
--- /dev/null
+++ b/tsc/internal/fourslash/tests/codeFixAddMissingAttributes12_test.go
@@ -0,0 +1,255 @@
+package fourslash_test
+
+import (
+ "strconv"
+ "testing"
+
+ "github.com/microsoft/TypeScript/tsc/internal/diagnostics"
+ "github.com/microsoft/TypeScript/tsc/internal/fourslash"
+ "github.com/microsoft/TypeScript/tsc/internal/locale"
+ "github.com/microsoft/TypeScript/tsc/internal/testutil"
+)
+
+func TestCodeFixAddMissingAttributes12(t *testing.T) {
+ t.Parallel()
+ const content = `// @jsx: preserve
+// @Filename: a.tsx
+interface T { a: number }
+const A = (a: T) => null;
+const a: T = {};
+const b: T = {};
+const c = ;
+const d = ;`
+ for i, test := range []struct {
+ fixID string
+ description string
+ result string
+ errors int
+ }{
+ {
+ fixID: "fixMissingProperties",
+ description: diagnostics.Add_all_missing_properties.Localize(locale.Default),
+ result: `interface T { a: number }
+const A = (a: T) => null;
+const a: T = {
+ a: 0
+};
+const b: T = {
+ a: 0
+};
+const c = ;
+const d = ;`,
+ errors: 2,
+ },
+ {
+ fixID: "fixMissingAttributes",
+ description: diagnostics.Add_all_missing_attributes.Localize(locale.Default),
+ result: `interface T { a: number }
+const A = (a: T) => null;
+const a: T = {};
+const b: T = {};
+const c = ;
+const d = ;`,
+ errors: 2,
+ },
+ {
+ result: `interface T { a: number }
+const A = (a: T) => null;
+const a: T = {
+ a: 0
+};
+const b: T = {
+ a: 0
+};
+const c = ;
+const d = ;`,
+ },
+ } {
+ t.Run(strconv.Itoa(i+1), 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.VerifyNumberOfErrorsInCurrentFile(t, 4)
+ if test.fixID == "" {
+ f.VerifySourceFixAll(t, test.result)
+ } else {
+ f.VerifyCodeFixAll(t, fourslash.VerifyCodeFixAllOptions{
+ FixID: test.fixID,
+ Description: test.description,
+ NewFileContent: test.result,
+ })
+ }
+ f.VerifyNumberOfErrorsInCurrentFile(t, test.errors)
+ })
+ }
+ for i, test := range []struct {
+ content string
+ result string
+ filename string
+ }{
+ {
+ content: `// @jsx: preserve
+// @Filename: /a.ts
+export class C {}
+export enum E { a, b }
+// @Filename: /b.tsx
+import type { C, E } from "./a";
+const A = (a: { a: E }) => null;
+const a: { a: C } = {};
+const b = ;`,
+ result: `import { C, E } from "./a";
+const A = (a: { a: E }) => null;
+const a: { a: C } = {
+ a: new C
+};
+const b = ;`,
+ filename: "/b.tsx",
+ },
+ {
+ content: `// @jsx: preserve
+// @Filename: a.tsx
+const A = (a: { a: { a: number }, b: number }) => null;
+const a = ({}))()} />;`,
+ result: `const A = (a: { a: { a: number }, b: number }) => null;
+const a = ({
+ a: 0
+}))()} b={0} />;`,
+ filename: "a.tsx",
+ },
+ {
+ content: `// @jsx: preserve
+// @verbatimModuleSyntax: true
+// @Filename: /a.ts
+export class C {}
+export enum E { a, b }
+export interface T { a: number }
+// @Filename: /b.tsx
+import type { C, E, T } from "./a";
+const A = (a: { a: E }) => null;
+const a: { a: C } = {};
+const b = ;
+const c = E.b;
+const d: T = { a: 0 };`,
+ result: `import { C, E, type T } from "./a";
+const A = (a: { a: E }) => null;
+const a: { a: C } = {
+ a: new C
+};
+const b = ;
+const c = E.b;
+const d: T = { a: 0 };`,
+ filename: "/b.tsx",
+ },
+ {
+ content: `// @jsx: preserve
+// @lib: es2015
+// @Filename: /a.ts
+export const a = Symbol();
+export class C {}
+export enum E { a, b }
+export type T = { [a]: C };
+export type U = { a: E };
+// @Filename: /b.tsx
+import type { T, U } from "./a";
+const A = (a: U) => null;
+const b: T = {};
+const c = ;`,
+ result: `import { a, C, E, type T, type U } from "./a";
+const A = (a: U) => null;
+const b: T = {
+ [a]: new C
+};
+const c = ;`,
+ filename: "/b.tsx",
+ },
+ {
+ content: `// @jsx: preserve
+// @Filename: a.tsx
+const A = (a: { a: number, b: number }) => null;
+const a = ({}))()} />;`,
+ result: `const A = (a: { a: number, b: number }) => null;
+const a = ({
+ a: 0
+}))()} />;`,
+ },
+ {
+ content: `// @jsx: preserve
+// @strictPropertyInitialization: false
+// @Filename: /a.ts
+export enum E { a, b }
+export interface T { a: E }
+// @Filename: /b.tsx
+import type { T } from "./a";
+class C implements T {}
+const A = (a: T) => null;
+const a = ;`,
+ result: `import { E, type T } from "./a";
+class C implements T {
+ a: E;
+}
+const A = (a: T) => null;
+const a = ;`,
+ filename: "/b.tsx",
+ },
+ {
+ content: `// @jsx: preserve
+// @declaration: true
+// @isolatedDeclarations: true
+// @Filename: /a.ts
+export enum E { a, b }
+// @Filename: /b.tsx
+import type { E } from "./a";
+const A = (a: { a: E }) => null;
+export function a() { return Math.abs(0); }
+const b: { a: E } = {};
+const c = ;`,
+ result: `import { E } from "./a";
+const A = (a: { a: E }) => null;
+export function a(): number { return Math.abs(0); }
+const b: { a: E } = {
+ a: E.a
+};
+const c = ;`,
+ filename: "/b.tsx",
+ },
+ {
+ content: `// @jsx: preserve
+// @declaration: true
+// @isolatedDeclarations: true
+// @verbatimModuleSyntax: true
+// @Filename: /a.ts
+export class C {}
+export interface T { a: C }
+export interface U { a: number }
+// @Filename: /b.tsx
+import type { T } from "./a";
+declare function a(): import("./a").U;
+const A = (a: T) => null;
+export function b() { return a(); }
+const c: T = {};
+const d = ;`,
+ result: `import { C, type T, type U } from "./a";
+declare function a(): import("./a").U;
+const A = (a: T) => null;
+export function b(): U { return a(); }
+const c: T = {
+ a: new C
+};
+const d = ;`,
+ filename: "/b.tsx",
+ },
+ } {
+ t.Run(strconv.Itoa(i+4), func(t *testing.T) {
+ t.Parallel()
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, test.content)
+ defer done()
+ if test.filename != "" {
+ f.GoToFile(t, test.filename)
+ }
+ f.VerifySourceFixAll(t, test.result)
+ f.VerifyNoErrors(t)
+ })
+ }
+}
diff --git a/tsc/internal/fourslash/tests/codeFixAddMissingAttributes1_test.go b/tsc/internal/fourslash/tests/codeFixAddMissingAttributes1_test.go
new file mode 100644
index 0000000000000..a455d7d7038f2
--- /dev/null
+++ b/tsc/internal/fourslash/tests/codeFixAddMissingAttributes1_test.go
@@ -0,0 +1,43 @@
+package fourslash_test
+
+import (
+ "testing"
+
+ "github.com/microsoft/TypeScript/tsc/internal/diagnostics"
+ "github.com/microsoft/TypeScript/tsc/internal/fourslash"
+ "github.com/microsoft/TypeScript/tsc/internal/locale"
+ "github.com/microsoft/TypeScript/tsc/internal/testutil"
+)
+
+const addMissingAttributesJsxDeclarations = `
+declare namespace JSX {
+ interface Element {}
+ interface IntrinsicElements {
+ div: {};
+ }
+}`
+
+func TestCodeFixAddMissingAttributes1(t *testing.T) {
+ t.Parallel()
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ const content = `// @jsx: preserve
+// @Filename: a.tsx
+interface P {
+ a: number;
+ b: string;
+}
+
+const A = ({ a, b }: P) =>
+ {a}{b}
;
+
+const B = () =>
+ `
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content+addMissingAttributesJsxDeclarations)
+ defer done()
+ f.VerifyCodeFix(t, fourslash.VerifyCodeFixOptions{
+ Description: diagnostics.Add_missing_attributes.Localize(locale.Default),
+ NewRangeContent: ` a={0} b={""}`,
+ ApplyChanges: true,
+ })
+ f.VerifyNoErrors(t)
+}
diff --git a/tsc/internal/fourslash/tests/codeFixAddMissingAttributes2_test.go b/tsc/internal/fourslash/tests/codeFixAddMissingAttributes2_test.go
new file mode 100644
index 0000000000000..59bd0064d7b68
--- /dev/null
+++ b/tsc/internal/fourslash/tests/codeFixAddMissingAttributes2_test.go
@@ -0,0 +1,35 @@
+package fourslash_test
+
+import (
+ "testing"
+
+ "github.com/microsoft/TypeScript/tsc/internal/diagnostics"
+ "github.com/microsoft/TypeScript/tsc/internal/fourslash"
+ "github.com/microsoft/TypeScript/tsc/internal/locale"
+ "github.com/microsoft/TypeScript/tsc/internal/testutil"
+)
+
+func TestCodeFixAddMissingAttributes2(t *testing.T) {
+ t.Parallel()
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ const content = `// @jsx: preserve
+// @Filename: a.tsx
+interface P {
+ a: number;
+ b: string;
+}
+
+const A = ({ a, b }: P) =>
+ {a}{b}
;
+
+const B = () =>
+ `
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content+addMissingAttributesJsxDeclarations)
+ defer done()
+ f.VerifyCodeFix(t, fourslash.VerifyCodeFixOptions{
+ Description: diagnostics.Add_missing_attributes.Localize(locale.Default),
+ NewRangeContent: ` b={""}`,
+ ApplyChanges: true,
+ })
+ f.VerifyNoErrors(t)
+}
diff --git a/tsc/internal/fourslash/tests/codeFixAddMissingAttributes3_test.go b/tsc/internal/fourslash/tests/codeFixAddMissingAttributes3_test.go
new file mode 100644
index 0000000000000..675082a5850a4
--- /dev/null
+++ b/tsc/internal/fourslash/tests/codeFixAddMissingAttributes3_test.go
@@ -0,0 +1,37 @@
+package fourslash_test
+
+import (
+ "testing"
+
+ "github.com/microsoft/TypeScript/tsc/internal/diagnostics"
+ "github.com/microsoft/TypeScript/tsc/internal/fourslash"
+ "github.com/microsoft/TypeScript/tsc/internal/locale"
+ "github.com/microsoft/TypeScript/tsc/internal/testutil"
+)
+
+func TestCodeFixAddMissingAttributes3(t *testing.T) {
+ t.Parallel()
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ const content = `// @jsx: preserve
+// @Filename: a.tsx
+interface P {
+ a: number;
+ b: string;
+ c: number[];
+ d: any;
+}
+
+const A = ({ a, b, c, d }: P) =>
+ {a}{b}{c}{d}
;
+
+const B = () =>
+ `
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content+addMissingAttributesJsxDeclarations)
+ defer done()
+ f.VerifyCodeFix(t, fourslash.VerifyCodeFixOptions{
+ Description: diagnostics.Add_missing_attributes.Localize(locale.Default),
+ NewRangeContent: ` c={[]} d={undefined}`,
+ ApplyChanges: true,
+ })
+ f.VerifyNoErrors(t)
+}
diff --git a/tsc/internal/fourslash/tests/codeFixAddMissingAttributes4_test.go b/tsc/internal/fourslash/tests/codeFixAddMissingAttributes4_test.go
new file mode 100644
index 0000000000000..f993f9800ea3c
--- /dev/null
+++ b/tsc/internal/fourslash/tests/codeFixAddMissingAttributes4_test.go
@@ -0,0 +1,38 @@
+package fourslash_test
+
+import (
+ "testing"
+
+ "github.com/microsoft/TypeScript/tsc/internal/diagnostics"
+ "github.com/microsoft/TypeScript/tsc/internal/fourslash"
+ "github.com/microsoft/TypeScript/tsc/internal/locale"
+ "github.com/microsoft/TypeScript/tsc/internal/testutil"
+)
+
+func TestCodeFixAddMissingAttributes4(t *testing.T) {
+ t.Parallel()
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ const content = `// @jsx: preserve
+// @Filename: a.tsx
+interface P {
+ a: number;
+ b: string;
+ c: number[];
+ d: any;
+}
+
+const A = ({ a, b, c, d }: P) =>
+ {a}{b}{c}{d}
;
+
+const b = { a: 1, c: [] };
+const B = () =>
+ `
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content+addMissingAttributesJsxDeclarations)
+ defer done()
+ f.VerifyCodeFix(t, fourslash.VerifyCodeFixOptions{
+ Description: diagnostics.Add_missing_attributes.Localize(locale.Default),
+ NewRangeContent: ` b={""} d={undefined}`,
+ ApplyChanges: true,
+ })
+ f.VerifyNoErrors(t)
+}
diff --git a/tsc/internal/fourslash/tests/codeFixAddMissingAttributes5_test.go b/tsc/internal/fourslash/tests/codeFixAddMissingAttributes5_test.go
index 8060af4c7a9b0..845247ca693fd 100644
--- a/tsc/internal/fourslash/tests/codeFixAddMissingAttributes5_test.go
+++ b/tsc/internal/fourslash/tests/codeFixAddMissingAttributes5_test.go
@@ -3,7 +3,9 @@ package fourslash_test
import (
"testing"
+ "github.com/microsoft/TypeScript/tsc/internal/diagnostics"
"github.com/microsoft/TypeScript/tsc/internal/fourslash"
+ "github.com/microsoft/TypeScript/tsc/internal/locale"
"github.com/microsoft/TypeScript/tsc/internal/testutil"
)
@@ -23,8 +25,9 @@ const A = ({ a, b, c, d }: P) =>
{a}{b}{c}{d}
;
const Bar = () =>
- [||]`
- f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
+ `
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content+addMissingAttributesJsxDeclarations)
defer done()
- f.VerifyCodeFixNotAvailable(t, "fixMissingAttributes")
+ f.VerifyNoErrors(t)
+ f.VerifyCodeFixNotAvailable(t, diagnostics.Add_missing_attributes.Localize(locale.Default))
}
diff --git a/tsc/internal/fourslash/tests/codeFixAddMissingAttributes6_test.go b/tsc/internal/fourslash/tests/codeFixAddMissingAttributes6_test.go
index 3d389911e22e1..6a34cf775f382 100644
--- a/tsc/internal/fourslash/tests/codeFixAddMissingAttributes6_test.go
+++ b/tsc/internal/fourslash/tests/codeFixAddMissingAttributes6_test.go
@@ -3,7 +3,9 @@ package fourslash_test
import (
"testing"
+ "github.com/microsoft/TypeScript/tsc/internal/diagnostics"
"github.com/microsoft/TypeScript/tsc/internal/fourslash"
+ "github.com/microsoft/TypeScript/tsc/internal/locale"
"github.com/microsoft/TypeScript/tsc/internal/testutil"
)
@@ -24,8 +26,9 @@ const A = ({ a, b, c, d }: P) =>
const props = { a: 1, b: "", c: [], d: undefined };
const Bar = () =>
- [||]`
- f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
+ `
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content+addMissingAttributesJsxDeclarations)
defer done()
- f.VerifyCodeFixNotAvailable(t, "fixMissingAttributes")
+ f.VerifyNoErrors(t)
+ f.VerifyCodeFixNotAvailable(t, diagnostics.Add_missing_attributes.Localize(locale.Default))
}
diff --git a/tsc/internal/fourslash/tests/codeFixAddMissingAttributes7_test.go b/tsc/internal/fourslash/tests/codeFixAddMissingAttributes7_test.go
new file mode 100644
index 0000000000000..94e7196efcf38
--- /dev/null
+++ b/tsc/internal/fourslash/tests/codeFixAddMissingAttributes7_test.go
@@ -0,0 +1,35 @@
+package fourslash_test
+
+import (
+ "testing"
+
+ "github.com/microsoft/TypeScript/tsc/internal/diagnostics"
+ "github.com/microsoft/TypeScript/tsc/internal/fourslash"
+ "github.com/microsoft/TypeScript/tsc/internal/locale"
+ "github.com/microsoft/TypeScript/tsc/internal/testutil"
+)
+
+func TestCodeFixAddMissingAttributes7(t *testing.T) {
+ t.Parallel()
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ const content = `// @jsx: preserve
+// @Filename: a.tsx
+interface P {
+ a: number;
+ b?: string;
+}
+
+const A = ({ a, b }: P) =>
+ {a}{b}
;
+
+const B = () =>
+ `
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content+addMissingAttributesJsxDeclarations)
+ defer done()
+ f.VerifyCodeFix(t, fourslash.VerifyCodeFixOptions{
+ Description: diagnostics.Add_missing_attributes.Localize(locale.Default),
+ NewRangeContent: ` a={0}`,
+ ApplyChanges: true,
+ })
+ f.VerifyNoErrors(t)
+}
diff --git a/tsc/internal/fourslash/tests/codeFixAddMissingAttributes8_test.go b/tsc/internal/fourslash/tests/codeFixAddMissingAttributes8_test.go
new file mode 100644
index 0000000000000..b4abf133dd9cc
--- /dev/null
+++ b/tsc/internal/fourslash/tests/codeFixAddMissingAttributes8_test.go
@@ -0,0 +1,34 @@
+package fourslash_test
+
+import (
+ "testing"
+
+ "github.com/microsoft/TypeScript/tsc/internal/diagnostics"
+ "github.com/microsoft/TypeScript/tsc/internal/fourslash"
+ "github.com/microsoft/TypeScript/tsc/internal/locale"
+ "github.com/microsoft/TypeScript/tsc/internal/testutil"
+)
+
+func TestCodeFixAddMissingAttributes8(t *testing.T) {
+ t.Parallel()
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ const content = `// @jsx: preserve
+// @Filename: a.tsx
+type A = 'a' | 'b';
+type B = 'd' | 'c';
+type C = ` + "`" + `${A}${B}` + "`" + `;
+
+const A = (b: { [K in C]: K }) =>
+ ;
+
+const B = () =>
+ `
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content+addMissingAttributesJsxDeclarations)
+ defer done()
+ f.VerifyCodeFix(t, fourslash.VerifyCodeFixOptions{
+ Description: diagnostics.Add_missing_attributes.Localize(locale.Default),
+ NewRangeContent: ` ac={"ac"} ad={"ad"} bc={"bc"} bd={"bd"}`,
+ ApplyChanges: true,
+ })
+ f.VerifyNoErrors(t)
+}
diff --git a/tsc/internal/fourslash/tests/codeFixAddMissingAttributes9_test.go b/tsc/internal/fourslash/tests/codeFixAddMissingAttributes9_test.go
new file mode 100644
index 0000000000000..bb7f50043bfdf
--- /dev/null
+++ b/tsc/internal/fourslash/tests/codeFixAddMissingAttributes9_test.go
@@ -0,0 +1,34 @@
+package fourslash_test
+
+import (
+ "testing"
+
+ "github.com/microsoft/TypeScript/tsc/internal/diagnostics"
+ "github.com/microsoft/TypeScript/tsc/internal/fourslash"
+ "github.com/microsoft/TypeScript/tsc/internal/locale"
+ "github.com/microsoft/TypeScript/tsc/internal/testutil"
+)
+
+func TestCodeFixAddMissingAttributes9(t *testing.T) {
+ t.Parallel()
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ const content = `// @jsx: preserve
+// @Filename: a.tsx
+type A = 'a-' | 'b-';
+type B = 'd' | 'c';
+type C = ` + "`" + `${A}${B}` + "`" + `;
+
+const A = (b: { [K in C]: K }) =>
+ ;
+
+const B = () =>
+ `
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content+addMissingAttributesJsxDeclarations)
+ defer done()
+ f.VerifyCodeFix(t, fourslash.VerifyCodeFixOptions{
+ Description: diagnostics.Add_missing_attributes.Localize(locale.Default),
+ NewRangeContent: ` a-c={"a-c"} a-d={"a-d"} b-c={"b-c"} b-d={"b-d"}`,
+ ApplyChanges: true,
+ })
+ f.VerifyNoErrors(t)
+}
diff --git a/tsc/internal/fourslash/tests/codeFixAddMissingAttributes_all_test.go b/tsc/internal/fourslash/tests/codeFixAddMissingAttributes_all_test.go
new file mode 100644
index 0000000000000..1226504133710
--- /dev/null
+++ b/tsc/internal/fourslash/tests/codeFixAddMissingAttributes_all_test.go
@@ -0,0 +1,60 @@
+package fourslash_test
+
+import (
+ "testing"
+
+ "github.com/microsoft/TypeScript/tsc/internal/diagnostics"
+ "github.com/microsoft/TypeScript/tsc/internal/fourslash"
+ "github.com/microsoft/TypeScript/tsc/internal/locale"
+ "github.com/microsoft/TypeScript/tsc/internal/testutil"
+)
+
+func TestCodeFixAddMissingAttributes_all(t *testing.T) {
+ t.Parallel()
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ const content = `// @jsx: preserve
+// @Filename: a.tsx
+interface P {
+ a: number;
+ b: string;
+ c: number[];
+ d: any;
+}
+const A = ({ a, b, c, d }: P) =>
+ {a}{b}{c}{d}
;
+const b = { a: 1, b: "" };
+
+const C1 = () =>
+
+const C2 = () =>
+
+const C3 = () =>
+
+const C4 = () =>
+ `
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content+addMissingAttributesJsxDeclarations)
+ defer done()
+ f.VerifyCodeFixAll(t, fourslash.VerifyCodeFixAllOptions{
+ FixID: "fixMissingAttributes",
+ Description: diagnostics.Add_all_missing_attributes.Localize(locale.Default),
+ NewFileContent: `interface P {
+ a: number;
+ b: string;
+ c: number[];
+ d: any;
+}
+const A = ({ a, b, c, d }: P) =>
+ {a}{b}{c}{d}
;
+const b = { a: 1, b: "" };
+
+const C1 = () =>
+
+const C2 = () =>
+
+const C3 = () =>
+
+const C4 = () =>
+ ` + addMissingAttributesJsxDeclarations,
+ })
+ f.VerifyNoErrors(t)
+}
diff --git a/tsc/internal/fourslash/tests/codeFixAddMissingProperties10_test.go b/tsc/internal/fourslash/tests/codeFixAddMissingProperties10_test.go
new file mode 100644
index 0000000000000..8f480ad057e90
--- /dev/null
+++ b/tsc/internal/fourslash/tests/codeFixAddMissingProperties10_test.go
@@ -0,0 +1,991 @@
+package fourslash_test
+
+import (
+ "strconv"
+ "testing"
+
+ "github.com/microsoft/TypeScript/tsc/internal/diagnostics"
+ "github.com/microsoft/TypeScript/tsc/internal/fourslash"
+ "github.com/microsoft/TypeScript/tsc/internal/locale"
+ "github.com/microsoft/TypeScript/tsc/internal/ls/lsutil"
+ "github.com/microsoft/TypeScript/tsc/internal/testutil"
+)
+
+func TestCodeFixAddMissingProperties10(t *testing.T) {
+ t.Parallel()
+ for i, test := range []struct {
+ content string
+ result string
+ filename string
+ fullFile bool
+ preferences *lsutil.UserPreferences
+ }{
+ {
+ content: `// @strict: false
+interface Foo {
+ a: number;
+ b: string;
+ c: 1;
+ d: "d";
+ e: "e1" | "e2";
+ f(x: number, y: number): void;
+ g: (x: number, y: number) => void;
+ h: number[];
+ i: bigint;
+ j: undefined | "special-string";
+ k: ` + "`" + `--${string}` + "`" + `;
+}
+const foo: Foo = [|{}|];`,
+ result: `{
+ a: 0,
+ b: "",
+ c: 1,
+ d: "d",
+ e: "e1",
+ f: function(x: number, y: number): void {
+ throw new Error("Function not implemented.");
+ },
+ g: function(x: number, y: number): void {
+ throw new Error("Function not implemented.");
+ },
+ h: [],
+ i: 0n,
+ j: "special-string",
+ k: ""
+}`,
+ },
+ {
+ content: `interface Foo {
+ a: number;
+ b: string;
+ c: any;
+}
+class C {
+ public c: Foo = [|{}|];
+}`,
+ result: `{
+ a: 0,
+ b: "",
+ c: undefined
+ }`,
+ },
+ {
+ content: `interface Foo {
+ a: number;
+ b: string;
+}
+function fn(foo: Foo = [|{}|]) {
+}`,
+ result: `{
+ a: 0,
+ b: ""
+}`,
+ },
+ {
+ content: `interface Foo {
+ a: number;
+ b: string;
+}
+const foo: Foo = [|{ a: 10 }|];`,
+ result: `{
+ a: 10,
+ b: ""
+}`,
+ },
+ {
+ content: `type T = {
+ a: null;
+}
+
+const foo: T = [|{}|];`,
+ result: `{
+ a: null
+}`,
+ },
+ {
+ content: `// @strict: false
+interface I {
+ x: number;
+ y: number;
+}
+class C {
+ public p: number;
+ m(x: number, y: I) {}
+}
+const foo: C = [|{}|];`,
+ result: `{
+ p: 0,
+ m: function(x: number, y: I): void {
+ throw new Error("Function not implemented.");
+ }
+}`,
+ },
+ {
+ content: `enum E1 {
+ A, B
+}
+enum E2 {
+ A
+}
+enum E3 {
+}
+interface I {
+ x: E1;
+ y: E2;
+ z: E3;
+}
+const foo: I = [|{}|];`,
+ result: `{
+ x: E1.A,
+ y: E2.A,
+ z: 0
+}`,
+ },
+ {
+ content: `class A {
+ constructor() {}
+}
+
+abstract class B {}
+
+class C {
+ constructor(a: string, b: number, c: A) {}
+}
+
+interface I {
+ a: A;
+ b: B;
+ c: C;
+}
+const foo: I = [|{}|];`,
+ result: `{
+ a: new A,
+ b: undefined,
+ c: undefined
+}`,
+ },
+ {
+ content: `interface I {
+ a: {
+ x: number;
+ y: { z: string; };
+ }
+}
+const foo: I = [|{}|];`,
+ result: `{
+ a: {
+ x: 0,
+ y: {
+ z: ""
+ }
+ }
+}`,
+ },
+ {
+ content: `type T = { x: number; };
+interface I {
+ a: T
+}
+const foo: I = [|{}|];`,
+ result: `{
+ a: {
+ x: 0
+ }
+}`,
+ },
+ {
+ content: `interface Foo {
+ a: ` + "`" + `--${string}` + "`" + `;
+ b: string;
+ c: "a" | "b"
+}
+const foo: Foo = [|{}|];`,
+ result: `{
+ a: '',
+ b: '',
+ c: 'a'
+}`,
+ preferences: &lsutil.UserPreferences{QuotePreference: "single"},
+ },
+ {
+ content: `interface Foo {
+ a: number;
+ b: number;
+}
+function f(foo: Foo) {}
+f([|{}|]);`,
+ result: `{
+ a: 0,
+ b: 0
+}`,
+ },
+ {
+ content: `interface Foo {
+ a: number;
+ b: number;
+ c: () => void;
+}
+function f(foo: Foo) {}
+f([|{ a: 10 }|]);`,
+ result: `{
+ a: 10,
+ b: 0,
+ c: function(): void {
+ throw new Error("Function not implemented.");
+ }
+}`,
+ },
+ {
+ content: `interface Foo {
+ a: number;
+ b: number;
+}
+function f(a: number, b: number, c: Foo) {}
+f(1, 2, [|{}|]);`,
+ result: `{
+ a: 0,
+ b: 0
+}`,
+ },
+ {
+ content: `interface Foo {
+ 1: number;
+ 2: number;
+}
+const foo: Foo = [|{}|]`,
+ result: `{
+ 1: 0,
+ 2: 0
+}`,
+ },
+ {
+ content: `type A = 'a' | 'b' | 'c' | 'd' | 'e';
+type B = 1 | 2 | 3;
+type C = '@' | '!';
+type D = ` + "`" + `${A}${Uppercase}${B}${C}` + "`" + `;
+
+const names: { [K in D]: K } = [|{}|];`,
+ result: `{
+ "aA1!": "aA1!",
+ "aA1@": "aA1@",
+ "aA2!": "aA2!",
+ "aA2@": "aA2@",
+ "aA3!": "aA3!",
+ "aA3@": "aA3@",
+ "aB1!": "aB1!",
+ "aB1@": "aB1@",
+ "aB2!": "aB2!",
+ "aB2@": "aB2@",
+ "aB3!": "aB3!",
+ "aB3@": "aB3@",
+ "aC1!": "aC1!",
+ "aC1@": "aC1@",
+ "aC2!": "aC2!",
+ "aC2@": "aC2@",
+ "aC3!": "aC3!",
+ "aC3@": "aC3@",
+ "aD1!": "aD1!",
+ "aD1@": "aD1@",
+ "aD2!": "aD2!",
+ "aD2@": "aD2@",
+ "aD3!": "aD3!",
+ "aD3@": "aD3@",
+ "aE1!": "aE1!",
+ "aE1@": "aE1@",
+ "aE2!": "aE2!",
+ "aE2@": "aE2@",
+ "aE3!": "aE3!",
+ "aE3@": "aE3@",
+ "bA1!": "bA1!",
+ "bA1@": "bA1@",
+ "bA2!": "bA2!",
+ "bA2@": "bA2@",
+ "bA3!": "bA3!",
+ "bA3@": "bA3@",
+ "bB1!": "bB1!",
+ "bB1@": "bB1@",
+ "bB2!": "bB2!",
+ "bB2@": "bB2@",
+ "bB3!": "bB3!",
+ "bB3@": "bB3@",
+ "bC1!": "bC1!",
+ "bC1@": "bC1@",
+ "bC2!": "bC2!",
+ "bC2@": "bC2@",
+ "bC3!": "bC3!",
+ "bC3@": "bC3@",
+ "bD1!": "bD1!",
+ "bD1@": "bD1@",
+ "bD2!": "bD2!",
+ "bD2@": "bD2@",
+ "bD3!": "bD3!",
+ "bD3@": "bD3@",
+ "bE1!": "bE1!",
+ "bE1@": "bE1@",
+ "bE2!": "bE2!",
+ "bE2@": "bE2@",
+ "bE3!": "bE3!",
+ "bE3@": "bE3@",
+ "cA1!": "cA1!",
+ "cA1@": "cA1@",
+ "cA2!": "cA2!",
+ "cA2@": "cA2@",
+ "cA3!": "cA3!",
+ "cA3@": "cA3@",
+ "cB1!": "cB1!",
+ "cB1@": "cB1@",
+ "cB2!": "cB2!",
+ "cB2@": "cB2@",
+ "cB3!": "cB3!",
+ "cB3@": "cB3@",
+ "cC1!": "cC1!",
+ "cC1@": "cC1@",
+ "cC2!": "cC2!",
+ "cC2@": "cC2@",
+ "cC3!": "cC3!",
+ "cC3@": "cC3@",
+ "cD1!": "cD1!",
+ "cD1@": "cD1@",
+ "cD2!": "cD2!",
+ "cD2@": "cD2@",
+ "cD3!": "cD3!",
+ "cD3@": "cD3@",
+ "cE1!": "cE1!",
+ "cE1@": "cE1@",
+ "cE2!": "cE2!",
+ "cE2@": "cE2@",
+ "cE3!": "cE3!",
+ "cE3@": "cE3@",
+ "dA1!": "dA1!",
+ "dA1@": "dA1@",
+ "dA2!": "dA2!",
+ "dA2@": "dA2@",
+ "dA3!": "dA3!",
+ "dA3@": "dA3@",
+ "dB1!": "dB1!",
+ "dB1@": "dB1@",
+ "dB2!": "dB2!",
+ "dB2@": "dB2@",
+ "dB3!": "dB3!",
+ "dB3@": "dB3@",
+ "dC1!": "dC1!",
+ "dC1@": "dC1@",
+ "dC2!": "dC2!",
+ "dC2@": "dC2@",
+ "dC3!": "dC3!",
+ "dC3@": "dC3@",
+ "dD1!": "dD1!",
+ "dD1@": "dD1@",
+ "dD2!": "dD2!",
+ "dD2@": "dD2@",
+ "dD3!": "dD3!",
+ "dD3@": "dD3@",
+ "dE1!": "dE1!",
+ "dE1@": "dE1@",
+ "dE2!": "dE2!",
+ "dE2@": "dE2@",
+ "dE3!": "dE3!",
+ "dE3@": "dE3@",
+ "eA1!": "eA1!",
+ "eA1@": "eA1@",
+ "eA2!": "eA2!",
+ "eA2@": "eA2@",
+ "eA3!": "eA3!",
+ "eA3@": "eA3@",
+ "eB1!": "eB1!",
+ "eB1@": "eB1@",
+ "eB2!": "eB2!",
+ "eB2@": "eB2@",
+ "eB3!": "eB3!",
+ "eB3@": "eB3@",
+ "eC1!": "eC1!",
+ "eC1@": "eC1@",
+ "eC2!": "eC2!",
+ "eC2@": "eC2@",
+ "eC3!": "eC3!",
+ "eC3@": "eC3@",
+ "eD1!": "eD1!",
+ "eD1@": "eD1@",
+ "eD2!": "eD2!",
+ "eD2@": "eD2@",
+ "eD3!": "eD3!",
+ "eD3@": "eD3@",
+ "eE1!": "eE1!",
+ "eE1@": "eE1@",
+ "eE2!": "eE2!",
+ "eE2@": "eE2@",
+ "eE3!": "eE3!",
+ "eE3@": "eE3@"
+}`,
+ },
+ {
+ content: `interface Foo {
+ foo(): T;
+}
+const x: Foo = [|{}|];`,
+ result: `{
+ foo: function(): string {
+ throw new Error("Function not implemented.");
+ }
+}`,
+ },
+ {
+ content: `interface Bar {
+ a: number;
+}
+
+interface Foo {
+ foo(a: T): U;
+}
+const x: Foo = [|{}|];`,
+ result: `{
+ foo: function(a: string): Bar {
+ throw new Error("Function not implemented.");
+ }
+}`,
+ },
+ {
+ content: `export interface Foo {
+ z(...args: T extends unknown[] ? T : [T]);
+}
+export interface Bar {
+ a(foo: Foo<[number]>): void;
+ b(foo: Foo<[]>): void;
+ c(foo: Foo): void;
+}
+const bar: Bar = [|{}|];`,
+ result: `{
+ a: function(foo: Foo<[number]>): void {
+ throw new Error("Function not implemented.");
+ },
+ b: function(foo: Foo<[]>): void {
+ throw new Error("Function not implemented.");
+ },
+ c: function(foo: Foo): void {
+ throw new Error("Function not implemented.");
+ }
+}`,
+ },
+ {
+ content: `type T = { foo: number };
+const foo: T[] = [];
+foo.push([|{ }|])`,
+ result: `{
+ foo: 0
+}`,
+ },
+ {
+ content: `// @Filename: /a.ts
+export type A = { x: string };
+// @Filename: /b.ts
+import { A } from "./a";
+export type Foo = { x: string };
+export interface B {
+ b(a: A): Foo;
+}
+// @Filename: /c.ts
+import { B } from "./b";
+const b: B = {};`,
+ result: `import { A } from "./a";
+import { B, Foo } from "./b";
+const b: B = {
+ b: function(a: A): Foo {
+ throw new Error("Function not implemented.");
+ }
+};`,
+ filename: "/c.ts",
+ fullFile: true,
+ },
+ {
+ content: `// @lib: es2020
+const x: Iterable = {}`,
+ result: `const x: Iterable = {
+ [Symbol.iterator]: function(): Iterator {
+ throw new Error("Function not implemented.");
+ }
+}`,
+ fullFile: true,
+ },
+ {
+ content: `enum E {
+ A
+}
+let obj: Record = {}`,
+ result: `enum E {
+ A
+}
+let obj: Record = {
+ [E.A]: undefined
+}`,
+ fullFile: true,
+ },
+ {
+ content: `interface A {
+ a: number;
+ b: string;
+}
+interface B {
+ c: boolean;
+}
+interface C {
+ a: A;
+ b: B;
+}
+function f(type: T, obj: C[T]): string {
+ return "";
+}
+f("a", [|{}|]);`,
+ result: `{
+ a: 0,
+ b: ""
+}`,
+ },
+ {
+ content: `// @allowJs: true
+// @checkJs: true
+// @Filename: /a.js
+/**
+ * @type {{ f: (x: string) => number }}
+ */
+export const foo = [|{}|]`,
+ result: `{
+ f: function(x) {
+ throw new Error("Function not implemented.");
+ }
+}`,
+ },
+ {
+ content: `// @allowJs: true
+// @checkJs: true
+// @Filename: /a.js
+/**
+ * @type {{ f: (x?: string) => number }}
+ */
+export const foo = [|{}|]`,
+ result: `{
+ f: function(x) {
+ throw new Error("Function not implemented.");
+ }
+}`,
+ },
+ {
+ content: `interface Foo {
+ a?: boolean;
+ b: boolean;
+}
+type A = { [K in keyof Foo]-?: { name: K, value: ` + "`" + `Foo ${Uppercase}` + "`" + ` }}
+const a: A = [|{}|];`,
+ result: `{
+ a: {
+ name: "a",
+ value: "Foo A"
+ },
+ b: {
+ name: "b",
+ value: "Foo B"
+ }
+}`,
+ },
+ {
+ content: `interface Foo {
+ a: number;
+ b: string;
+}
+
+interface Bar {
+ value: Foo;
+}
+
+const bar: Bar = {
+ value: [|{
+ a: 10
+ }|]
+}`,
+ result: `{
+ a: 10,
+ b: ""
+ }`,
+ },
+ {
+ content: `namespace Foo.Bar {
+ export enum E {
+ E1 = 0,
+ E2 = 1,
+ }
+ export interface Baz {
+ prop1: string;
+ prop2: number;
+ prop3: E.E1;
+ prop4: Foo.Bar.E.E1;
+ }
+}
+const foo: Foo.Bar.Baz = [|{}|]`,
+ result: `{
+ prop1: "",
+ prop2: 0,
+ prop3: Foo.Bar.E.E1,
+ prop4: Foo.Bar.E.E1
+}`,
+ },
+ {
+ content: `interface A {
+ a: number;
+ b: string;
+}
+function f(_obj: A[]): string {
+ return "";
+}
+f([[|{}|]])`,
+ result: `{
+ a: 0,
+ b: ""
+}`,
+ },
+ {
+ content: `interface A {
+ a: number;
+ b: string;
+}
+interface B {
+ c: A[];
+}
+const b: B[] = [{c: [[|{}|]]}]`,
+ result: `{
+ a: 0,
+ b: ""
+}`,
+ },
+ {
+ content: `// @Filename: E.ts
+export enum E {
+ A,
+ B,
+}
+// @Filename: foo.ts
+import { E } from "./E"
+type T = {
+ e: E,
+}
+const t: T = [|{ }|]`,
+ result: `{
+ e: E.A
+}`,
+ filename: "foo.ts",
+ },
+ {
+ content: `// @strict: false
+interface Foo {
+ a: number;
+ b: string;
+ c: 1;
+ d: "d";
+ e: "e1" | "e2";
+ f(x: number, y: number): void;
+ g: (x: number, y: number) => void;
+ h: number[];
+ i: bigint;
+ j: undefined | "special-string";
+ k: ` + "`" + `--${string}` + "`" + `;
+}
+const b = [|{}|] satisfies Foo;`,
+ result: `{
+ a: 0,
+ b: "",
+ c: 1,
+ d: "d",
+ e: "e1",
+ f: function(x: number, y: number): void {
+ throw new Error("Function not implemented.");
+ },
+ g: function(x: number, y: number): void {
+ throw new Error("Function not implemented.");
+ },
+ h: [],
+ i: 0n,
+ j: "special-string",
+ k: ""
+}`,
+ },
+ {
+ content: `interface Foo {
+ a: number;
+ b: string;
+ c: any;
+}
+class C {
+ public c = [|{}|] satisfies Foo;
+}`,
+ result: `{
+ a: 0,
+ b: "",
+ c: undefined
+ }`,
+ },
+ {
+ content: `interface Foo {
+ a: number;
+ b: string;
+}
+const foo = [|{ a: 10 }|] satisfies Foo;`,
+ result: `{
+ a: 10,
+ b: ""
+}`,
+ },
+ {
+ content: `type T = {
+ a: null;
+}
+
+const foo = [|{}|] satisfies T;`,
+ result: `{
+ a: null
+}`,
+ },
+ {
+ content: `// @strict: false
+interface I {
+ x: number;
+ y: number;
+}
+class C {
+ public p: number;
+ m(x: number, y: I) {}
+}
+const foo = [|{}|] satisfies C;`,
+ result: `{
+ p: 0,
+ m: function(x: number, y: I): void {
+ throw new Error("Function not implemented.");
+ }
+}`,
+ },
+ {
+ content: `enum E1 {
+ A, B
+}
+enum E2 {
+ A
+}
+enum E3 {
+}
+interface I {
+ x: E1;
+ y: E2;
+ z: E3;
+}
+const foo = [|{}|] satisfies I;`,
+ result: `{
+ x: E1.A,
+ y: E2.A,
+ z: 0
+}`,
+ },
+ {
+ content: `class A {
+ constructor() {}
+}
+
+abstract class B {}
+
+class C {
+ constructor(a: string, b: number, c: A) {}
+}
+
+interface I {
+ a: A;
+ b: B;
+ c: C;
+}
+const foo = [|{}|] satisfies I;`,
+ result: `{
+ a: new A,
+ b: undefined,
+ c: undefined
+}`,
+ },
+ {
+ content: `interface I {
+ a: {
+ x: number;
+ y: { z: string; };
+ }
+}
+const foo = [|{}|] satisfies I;`,
+ result: `{
+ a: {
+ x: 0,
+ y: {
+ z: ""
+ }
+ }
+}`,
+ },
+ {
+ content: `interface Bar {
+ a: number;
+}
+
+interface Foo {
+ foo(a: T): U;
+}
+const x = [|{}|] satisfies Foo;`,
+ result: `{
+ foo: function(a: string): Bar {
+ throw new Error("Function not implemented.");
+ }
+}`,
+ },
+ {
+ content: `type A = { a: string };
+type B = { b: string };
+
+const c = [|{ }|] satisfies A satisfies B;`,
+ result: `{
+ a: ""
+}`,
+ },
+ {
+ content: `// @strict: false
+interface Foo {
+ a: number;
+ b: string;
+ c: 1;
+ d: "d";
+ e: "e1" | "e2";
+ f(x: number, y: number): void;
+ g: (x: number, y: number) => void;
+ h: number[];
+ i: bigint;
+ j: undefined | "special-string";
+ k: ` + "`" + `--${string}` + "`" + `;
+}
+const f = (): Foo => {
+ return [|{ }|];
+};`,
+ result: `{
+ a: 0,
+ b: "",
+ c: 1,
+ d: "d",
+ e: "e1",
+ f: function(x: number, y: number): void {
+ throw new Error("Function not implemented.");
+ },
+ g: function(x: number, y: number): void {
+ throw new Error("Function not implemented.");
+ },
+ h: [],
+ i: 0n,
+ j: "special-string",
+ k: ""
+}`,
+ },
+ {
+ content: `// @strict: false
+// @lib: es2020
+// @target: es2020
+interface Foo {
+ a: number;
+ b: string;
+ c: 1;
+ d: "d";
+ e: "e1" | "e2";
+ f(x: number, y: number): void;
+ g: (x: number, y: number) => void;
+ h: number[];
+ i: bigint;
+ j: undefined | "special-string";
+ k: ` + "`" + `--${string}` + "`" + `;
+}
+const f = function* (): Generator {
+ yield [|{}|];
+};`,
+ result: `{
+ a: 0,
+ b: "",
+ c: 1,
+ d: "d",
+ e: "e1",
+ f: function(x: number, y: number): void {
+ throw new Error("Function not implemented.");
+ },
+ g: function(x: number, y: number): void {
+ throw new Error("Function not implemented.");
+ },
+ h: [],
+ i: 0n,
+ j: "special-string",
+ k: ""
+}`,
+ },
+ {
+ content: `// @strict: true
+ type U = { u?: { v: string } };
+ const u: U = { u: [|{}|] };`,
+ result: `{
+ v: ""
+}`,
+ },
+ {
+ content: `// @strict: true
+ type T = { t: string };
+ declare function f(arg?: T): void;
+ f([|{}|]);`,
+ result: `{
+ t: ""
+}`,
+ },
+ {
+ content: `// @strict: true
+ interface A {
+ a: number;
+ b: string;
+ }
+ function f(_obj: (A | undefined)[]): string {
+ return "";
+ }
+ f([[|{}|]]);`,
+ result: `{
+ a: 0,
+ b: ""
+}`,
+ },
+ {
+ content: `// @strict: true
+ interface A {
+ a: number;
+ b: string;
+ }
+ interface B {
+ c: (A | undefined)[];
+ }
+ const b: B[] = [{ c: [[|{}|]] }];`,
+ result: `{
+ a: 0,
+ b: ""
+}`,
+ },
+ } {
+ t.Run(strconv.Itoa(i+1), func(t *testing.T) {
+ t.Parallel()
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, test.content)
+ defer done()
+ if test.filename != "" {
+ f.GoToFile(t, test.filename)
+ }
+ options := fourslash.VerifyCodeFixOptions{
+ Description: diagnostics.Add_missing_properties.Localize(locale.Default),
+ ApplyChanges: true,
+ UserPreferences: test.preferences,
+ }
+ if test.fullFile {
+ options.NewFileContent = test.result
+ } else {
+ options.NewRangeContent = test.result
+ }
+ f.VerifyCodeFix(t, options)
+ })
+ }
+}
diff --git a/tsc/internal/fourslash/tests/codeFixAddMissingProperties11_test.go b/tsc/internal/fourslash/tests/codeFixAddMissingProperties11_test.go
new file mode 100644
index 0000000000000..b3d4c36469a68
--- /dev/null
+++ b/tsc/internal/fourslash/tests/codeFixAddMissingProperties11_test.go
@@ -0,0 +1,360 @@
+package fourslash_test
+
+import (
+ "strconv"
+ "testing"
+
+ "github.com/microsoft/TypeScript/tsc/internal/diagnostics"
+ "github.com/microsoft/TypeScript/tsc/internal/fourslash"
+ "github.com/microsoft/TypeScript/tsc/internal/locale"
+ "github.com/microsoft/TypeScript/tsc/internal/testutil"
+ "github.com/microsoft/TypeScript/tsc/internal/testutil/contentmappertest"
+)
+
+func TestCodeFixAddMissingProperties11(t *testing.T) {
+ t.Parallel()
+ t.Run("1", func(t *testing.T) {
+ t.Parallel()
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ const content = `interface Test {
+ foo: string;
+ bar(a: string): void;
+}
+function f (_spec: any) {}
+function g (_spec: Test) {}
+f(() => {
+ g({});
+ g(
+ {});
+ g(
+ {}
+ );
+});`
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
+ defer done()
+ f.VerifyCodeFixAll(t, fourslash.VerifyCodeFixAllOptions{
+ FixID: "fixMissingProperties",
+ Description: diagnostics.Add_all_missing_properties.Localize(locale.Default),
+ NewFileContent: `interface Test {
+ foo: string;
+ bar(a: string): void;
+}
+function f (_spec: any) {}
+function g (_spec: Test) {}
+f(() => {
+ g({
+ foo: "",
+ bar: function(a: string): void {
+ throw new Error("Function not implemented.");
+ }
+ });
+ g(
+ {
+ foo: "",
+ bar: function(a: string): void {
+ throw new Error("Function not implemented.");
+ }
+ });
+ g(
+ {
+ foo: "",
+ bar: function(a: string): void {
+ throw new Error("Function not implemented.");
+ }
+ }
+ );
+});`,
+ })
+ f.VerifyNoErrors(t)
+ })
+ t.Run("2", func(t *testing.T) {
+ t.Parallel()
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ const content = `// @lib: es2015
+// @Filename: /a.ts
+export const a = Symbol();
+export enum E { a, b }
+export class C {}
+export type T = { [a]: E; b: C };
+// @Filename: /b.ts
+import type { C as B, E, T } from "./a";
+const b: { b: B; c: E; d?: T } = {};
+const c: T = {};`
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
+ defer done()
+ f.GoToFile(t, "/b.ts")
+ f.VerifyCodeFixAll(t, fourslash.VerifyCodeFixAllOptions{
+ FixID: "fixMissingProperties",
+ Description: diagnostics.Add_all_missing_properties.Localize(locale.Default),
+ NewFileContent: `import { a, C as B, E, type T } from "./a";
+const b: { b: B; c: E; d?: T } = {
+ b: new B,
+ c: E.a
+};
+const c: T = {
+ [a]: E.a,
+ b: new B
+};`,
+ })
+ f.VerifyNoErrors(t)
+ })
+ t.Run("3", func(t *testing.T) {
+ t.Parallel()
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ const content = `type T = { a: () => { b: number }; c: number };
+const a: T = { a: (): { b: number } => ({}) };`
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
+ defer done()
+ f.VerifyCodeFixAll(t, fourslash.VerifyCodeFixAllOptions{
+ FixID: "fixMissingProperties",
+ Description: diagnostics.Add_all_missing_properties.Localize(locale.Default),
+ NewFileContent: `type T = { a: () => { b: number }; c: number };
+const a: T = { a: (): { b: number } => ({
+ b: 0
+}), c: 0 };`,
+ })
+ f.VerifyNoErrors(t)
+ })
+ t.Run("4", func(t *testing.T) {
+ t.Parallel()
+ // Canonical output is export {}; the supplemental script maps the whole input verbatim.
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ f, done := newContentMapperFourslash(t, `// @Filename: /a.ts
+export interface T { a: number }
+// @Filename: /b.astro
+import type { T } from "./a";
+const a: T = {};
+const b: T = {};
+`, contentmappertest.SupplementalMapper, ".astro")
+ defer done()
+ f.GoToFile(t, "/b.astro")
+ f.VerifyCodeFixAll(t, fourslash.VerifyCodeFixAllOptions{
+ FixID: "fixMissingProperties",
+ Description: diagnostics.Add_all_missing_properties.Localize(locale.Default),
+ NewFileContent: `import type { T } from "./a";
+const a: T = {
+ a: 0
+};
+const b: T = {
+ a: 0
+};
+`,
+ })
+ f.VerifyNoErrors(t)
+ })
+ t.Run("5", func(t *testing.T) {
+ t.Parallel()
+ // Virtual output starts with const __VERSION = "1.0.0"; and replaces #{target} with "es2020".
+ // The replacement is an atom span; the surrounding source, including the import, maps verbatim.
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ f, done := newContentMapperFourslash(t, `// @Filename: /a.ts
+export class C {}
+export enum E { a, b }
+// @Filename: /b.box
+import type { C } from "./a";
+const a: { b: unknown; c: C } = { b: #{target} };
+const b = E.a;
+`, contentmappertest.TransformingMapper, ".box")
+ defer done()
+ f.GoToFile(t, "/b.box")
+ f.VerifyNumberOfErrorsInCurrentFile(t, 2)
+ f.VerifySourceFixAll(t, `import { C, E } from "./a";
+const a: { b: unknown; c: C } = { b: #{target}, c: new C };
+const b = E.a;
+`)
+ f.VerifyNoErrors(t)
+ })
+ t.Run("6", func(t *testing.T) {
+ t.Parallel()
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ const content = `type T = { a: () => U; b: number };
+type U = { a: () => V; b: number };
+type V = { a: number };
+const a: T = { a: (): U => ({ a: (): V => ({}) }) };
+const b: V = {};`
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
+ defer done()
+ f.VerifySourceFixAll(t, `type T = { a: () => U; b: number };
+type U = { a: () => V; b: number };
+type V = { a: number };
+const a: T = { a: (): U => ({ a: (): V => ({
+ a: 0
+}), b: 0 }), b: 0 };
+const b: V = {
+ a: 0
+};`)
+ f.VerifyNoErrors(t)
+ })
+ t.Run("7", func(t *testing.T) {
+ t.Parallel()
+ // Supplemental output maps the entire input verbatim, optionally after a generated prefix.
+ // DuplicateProjectionMapper emits that same mapping in both canonical and supplemental scripts.
+ for i, mapper := range []string{contentmappertest.SupplementalMapper, contentmappertest.PrefixedSupplementalMapper, contentmappertest.DuplicateProjectionMapper, contentmappertest.SupplementalDiagnosticsMapper} {
+ t.Run(strconv.Itoa(i+1), func(t *testing.T) {
+ t.Parallel()
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ f, done := newContentMapperFourslash(t, `// @Filename: /a.ts
+export class C {}
+export const missingSupplementalGlobal = 0;
+// @Filename: /b.astro
+import type { C } from "./a";
+const a: { a: C } = {};
+const b: { a: C } = {};
+const c: { a: number; b: C; c: number } = {
+ a: 0
+};
+const d: { a: number; b: C; c: number } = {
+ a: 0, // a
+};
+`, mapper, ".astro")
+ defer done()
+ f.GoToFile(t, "/b.astro")
+ f.VerifySourceFixAll(t, `import { C } from "./a";
+const a: { a: C } = {
+ a: new C
+};
+const b: { a: C } = {
+ a: new C
+};
+const c: { a: number; b: C; c: number } = {
+ a: 0,
+ b: new C,
+ c: 0
+};
+const d: { a: number; b: C; c: number } = {
+ a: 0, // a
+ b: new C,
+ c: 0,
+};
+`)
+ if mapper == contentmappertest.SupplementalDiagnosticsMapper {
+ f.VerifyNumberOfErrorsInCurrentFile(t, 1)
+ } else {
+ f.VerifyNoErrors(t)
+ }
+ })
+ }
+ })
+ t.Run("8", func(t *testing.T) {
+ t.Parallel()
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ // A synthesized import type { C } from "./a"; precedes the verbatim supplemental script.
+ // The object replacements are writable, but promoting that import has no original location.
+ f, done := newContentMapperFourslash(t, `// @Filename: /a.ts
+export class C {}
+// @Filename: /b-codefix-unmapped-import.astro
+const /*1*/a: { a: C } = {};
+const b: { a: C } = {};
+`, contentmappertest.PrefixedSupplementalMapper, ".astro")
+ defer done()
+ f.GoToMarker(t, "1")
+ f.VerifyCodeFixNotAvailable(t)
+ })
+ t.Run("9", func(t *testing.T) {
+ t.Parallel()
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ // Both declarations map verbatim after a generated __VERSION header; imports use the first writable segment.
+ f, done := newContentMapperFourslash(t, `// @Filename: /a.ts
+export class C {}
+// @Filename: /b.box
+const a: { a: import("./a").C } = {};
+const b: { a: import("./a").C } = {};
+`, contentmappertest.TransformingMapper, ".box")
+ defer done()
+ f.GoToFile(t, "/b.box")
+ f.VerifySourceFixAll(t, `import { C } from "./a";
+
+const a: { a: import("./a").C } = {
+ a: new C
+};
+const b: { a: import("./a").C } = {
+ a: new C
+};
+`)
+ f.VerifyNoErrors(t)
+ })
+ t.Run("10", func(t *testing.T) {
+ t.Parallel()
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ const content = `// @Filename: /a.ts
+export enum E { a, b }
+// @Filename: /b.ts
+import * as ns from "./a";
+const a: { a: unknown; b: number } = { a: E.a };`
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
+ defer done()
+ f.GoToFile(t, "/b.ts")
+ f.VerifySourceFixAll(t, `import * as ns from "./a";
+const a: { a: unknown; b: number } = { a: ns.E.a, b: 0 };`)
+ f.VerifyNoErrors(t)
+ })
+ t.Run("11", func(t *testing.T) {
+ t.Parallel()
+ for i, test := range []struct {
+ literal string
+ result string
+ }{
+ {`{ a: 0 }`, `{ a: 0, b: 0, c: "" }`},
+ {`{ a: 0, }`, `{ a: 0, b: 0, c: "", }`},
+ {`{
+ a: 0
+}`, `{
+ a: 0,
+ b: 0,
+ c: ""
+}`},
+ {`{
+ a: 0,
+}`, `{
+ a: 0,
+ b: 0,
+ c: "",
+}`},
+ {`{
+ a: 0 // a
+}`, `{
+ a: 0, // a
+ b: 0,
+ c: ""
+}`},
+ {`{
+ a: 0, // a
+}`, `{
+ a: 0, // a
+ b: 0,
+ c: "",
+}`},
+ } {
+ t.Run(strconv.Itoa(i+1), func(t *testing.T) {
+ t.Parallel()
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ const prefix = "const a: { a: number; b: number; c: string } = "
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, prefix+test.literal+";")
+ defer done()
+ f.VerifySourceFixAll(t, prefix+test.result+";")
+ f.VerifyNoErrors(t)
+ })
+ }
+ })
+ t.Run("12", func(t *testing.T) {
+ t.Parallel()
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ const content = `// @Filename: /a.ts
+export enum E { a, b }
+// @Filename: /b.ts
+import * as ns from "./a";
+type T = { a: () => { a: unknown; b: number }; b: number; c: string };
+function a() {
+ const a: T = { a: (): { a: unknown; b: number } => ({ a: E.a }) };
+}`
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
+ defer done()
+ f.GoToFile(t, "/b.ts")
+ f.VerifySourceFixAll(t, `import * as ns from "./a";
+type T = { a: () => { a: unknown; b: number }; b: number; c: string };
+function a() {
+ const a: T = { a: (): { a: unknown; b: number } => ({ a: ns.E.a, b: 0 }), b: 0, c: "" };
+}`)
+ f.VerifyNoErrors(t)
+ })
+}
diff --git a/tsc/internal/fourslash/tests/codeFixAddMissingProperties1_test.go b/tsc/internal/fourslash/tests/codeFixAddMissingProperties1_test.go
new file mode 100644
index 0000000000000..ff53e9653b9f6
--- /dev/null
+++ b/tsc/internal/fourslash/tests/codeFixAddMissingProperties1_test.go
@@ -0,0 +1,29 @@
+package fourslash_test
+
+import (
+ "testing"
+
+ "github.com/microsoft/TypeScript/tsc/internal/diagnostics"
+ "github.com/microsoft/TypeScript/tsc/internal/fourslash"
+ "github.com/microsoft/TypeScript/tsc/internal/locale"
+ "github.com/microsoft/TypeScript/tsc/internal/testutil"
+)
+
+func TestCodeFixAddMissingProperties1(t *testing.T) {
+ t.Parallel()
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ const content = `type T = { a: number; b: string }
+const t: T = {}`
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
+ defer done()
+ f.VerifyCodeFix(t, fourslash.VerifyCodeFixOptions{
+ Description: diagnostics.Add_missing_properties.Localize(locale.Default),
+ NewFileContent: `type T = { a: number; b: string }
+const t: T = {
+ a: 0,
+ b: ""
+}`,
+ ApplyChanges: true,
+ })
+ f.VerifyNoErrors(t)
+}
diff --git a/tsc/internal/fourslash/tests/codeFixAddMissingProperties2_test.go b/tsc/internal/fourslash/tests/codeFixAddMissingProperties2_test.go
new file mode 100644
index 0000000000000..ac268b8e7f174
--- /dev/null
+++ b/tsc/internal/fourslash/tests/codeFixAddMissingProperties2_test.go
@@ -0,0 +1,110 @@
+package fourslash_test
+
+import (
+ "strconv"
+ "testing"
+
+ "github.com/microsoft/TypeScript/tsc/internal/diagnostics"
+ "github.com/microsoft/TypeScript/tsc/internal/fourslash"
+ "github.com/microsoft/TypeScript/tsc/internal/locale"
+ "github.com/microsoft/TypeScript/tsc/internal/testutil"
+)
+
+func TestCodeFixAddMissingProperties2(t *testing.T) {
+ t.Parallel()
+ for i, test := range []struct {
+ content string
+ result string
+ }{
+ {
+ `interface T { a: number; b: string; c?: boolean }
+const b = { a: 1 };
+const a: T = { ...b };`,
+ `interface T { a: number; b: string; c?: boolean }
+const b = { a: 1 };
+const a: T = {
+ ...b,
+ b: ""
+};`,
+ },
+ {
+ `// @allowJs: true
+// @checkJs: true
+// @Filename: a.js
+/** @type {{ a: number; b: string }} */
+const a = {};`,
+ `/** @type {{ a: number; b: string }} */
+const a = {
+ a: 0,
+ b: ""
+};`,
+ },
+ {
+ `const a: { b: { c: number } } = { b: {} };`,
+ `const a: { b: { c: number } } = { b: {
+ c: 0
+} };`,
+ },
+ {
+ `declare function a(...b: [{ c: number }]): void;
+a({});`,
+ `declare function a(...b: [{ c: number }]): void;
+a({
+ c: 0
+});`,
+ },
+ {
+ `declare function a(b: T): void;
+a({});`,
+ `declare function a(b: T): void;
+a({
+ b: 0
+});`,
+ },
+ {
+ `declare function a(...b: { c: number }[]): void;
+a({ c: 1 }, {});`,
+ `declare function a(...b: { c: number }[]): void;
+a({ c: 1 }, {
+ c: 0
+});`,
+ },
+ {
+ `declare function a(...b: [{ c: number }, { d: string }]): void;
+a({ c: 1 }, {});`,
+ `declare function a(...b: [{ c: number }, { d: string }]): void;
+a({ c: 1 }, {
+ d: ""
+});`,
+ },
+ {
+ `declare function a(...b: [number, ...{ c: number }[]]): void;
+a(1, { c: 1 }, {});`,
+ `declare function a(...b: [number, ...{ c: number }[]]): void;
+a(1, { c: 1 }, {
+ c: 0
+});`,
+ },
+ {
+ `declare function a(b: number, ...c: { d: string }[]): void;
+a(1, { d: "" }, {});`,
+ `declare function a(b: number, ...c: { d: string }[]): void;
+a(1, { d: "" }, {
+ d: ""
+});`,
+ },
+ } {
+ t.Run(strconv.Itoa(i+1), func(t *testing.T) {
+ t.Parallel()
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, test.content)
+ defer done()
+ f.VerifyCodeFix(t, fourslash.VerifyCodeFixOptions{
+ Description: diagnostics.Add_missing_properties.Localize(locale.Default),
+ NewFileContent: test.result,
+ ApplyChanges: true,
+ })
+ f.VerifyNoErrors(t)
+ })
+ }
+}
diff --git a/tsc/internal/fourslash/tests/codeFixAddMissingProperties3_test.go b/tsc/internal/fourslash/tests/codeFixAddMissingProperties3_test.go
new file mode 100644
index 0000000000000..483e02acba572
--- /dev/null
+++ b/tsc/internal/fourslash/tests/codeFixAddMissingProperties3_test.go
@@ -0,0 +1,143 @@
+package fourslash_test
+
+import (
+ "testing"
+
+ "github.com/microsoft/TypeScript/tsc/internal/diagnostics"
+ "github.com/microsoft/TypeScript/tsc/internal/fourslash"
+ "github.com/microsoft/TypeScript/tsc/internal/locale"
+ "github.com/microsoft/TypeScript/tsc/internal/testutil"
+)
+
+func TestCodeFixAddMissingProperties3(t *testing.T) {
+ t.Parallel()
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ const content = `interface I1 {
+ a: number;
+ b: string;
+ c: 1;
+ d: "d";
+ e: "e1" | "e2";
+ f(x: number, y: number): void;
+ g: (x: number, y: number) => void;
+}
+interface I2 {
+ a: unknown;
+ b: any;
+}
+const a: I1 = {};
+const b: I2 = {};
+class C1 {
+ public c: I1 = {};
+}
+function fn1(foo: I2 = {}) {}
+function fn2(a: I1) {}
+fn2({});
+const d = {} satisfies I1;
+const e = {} satisfies I2;
+class C2 {
+ public f = {} satisfies I1;
+}`
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
+ defer done()
+ f.VerifyCodeFixAll(t, fourslash.VerifyCodeFixAllOptions{
+ FixID: "fixMissingProperties",
+ Description: diagnostics.Add_all_missing_properties.Localize(locale.Default),
+ NewFileContent: `interface I1 {
+ a: number;
+ b: string;
+ c: 1;
+ d: "d";
+ e: "e1" | "e2";
+ f(x: number, y: number): void;
+ g: (x: number, y: number) => void;
+}
+interface I2 {
+ a: unknown;
+ b: any;
+}
+const a: I1 = {
+ a: 0,
+ b: "",
+ c: 1,
+ d: "d",
+ e: "e1",
+ f: function(x: number, y: number): void {
+ throw new Error("Function not implemented.");
+ },
+ g: function(x: number, y: number): void {
+ throw new Error("Function not implemented.");
+ }
+};
+const b: I2 = {
+ a: undefined,
+ b: undefined
+};
+class C1 {
+ public c: I1 = {
+ a: 0,
+ b: "",
+ c: 1,
+ d: "d",
+ e: "e1",
+ f: function(x: number, y: number): void {
+ throw new Error("Function not implemented.");
+ },
+ g: function(x: number, y: number): void {
+ throw new Error("Function not implemented.");
+ }
+ };
+}
+function fn1(foo: I2 = {
+ a: undefined,
+ b: undefined
+}) {}
+function fn2(a: I1) {}
+fn2({
+ a: 0,
+ b: "",
+ c: 1,
+ d: "d",
+ e: "e1",
+ f: function(x: number, y: number): void {
+ throw new Error("Function not implemented.");
+ },
+ g: function(x: number, y: number): void {
+ throw new Error("Function not implemented.");
+ }
+});
+const d = {
+ a: 0,
+ b: "",
+ c: 1,
+ d: "d",
+ e: "e1",
+ f: function(x: number, y: number): void {
+ throw new Error("Function not implemented.");
+ },
+ g: function(x: number, y: number): void {
+ throw new Error("Function not implemented.");
+ }
+} satisfies I1;
+const e = {
+ a: undefined,
+ b: undefined
+} satisfies I2;
+class C2 {
+ public f = {
+ a: 0,
+ b: "",
+ c: 1,
+ d: "d",
+ e: "e1",
+ f: function(x: number, y: number): void {
+ throw new Error("Function not implemented.");
+ },
+ g: function(x: number, y: number): void {
+ throw new Error("Function not implemented.");
+ }
+ } satisfies I1;
+}`,
+ })
+ f.VerifyNoErrors(t)
+}
diff --git a/tsc/internal/fourslash/tests/codeFixAddMissingProperties4_test.go b/tsc/internal/fourslash/tests/codeFixAddMissingProperties4_test.go
new file mode 100644
index 0000000000000..612b84697ebbf
--- /dev/null
+++ b/tsc/internal/fourslash/tests/codeFixAddMissingProperties4_test.go
@@ -0,0 +1,75 @@
+package fourslash_test
+
+import (
+ "testing"
+
+ "github.com/microsoft/TypeScript/tsc/internal/diagnostics"
+ "github.com/microsoft/TypeScript/tsc/internal/fourslash"
+ "github.com/microsoft/TypeScript/tsc/internal/locale"
+ "github.com/microsoft/TypeScript/tsc/internal/testutil"
+)
+
+func TestCodeFixAddMissingProperties4(t *testing.T) {
+ t.Parallel()
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ const content = `// @target: esnext
+// @strict: true
+type T = {
+ a: boolean;
+ b: 1;
+ c: -1;
+ d: "a";
+ e: true;
+ f: false;
+ g: bigint;
+ h: -1n;
+ i: null;
+ j: number[];
+ k: { a: string };
+ l: string | number;
+ m: unknown;
+ n: undefined;
+};
+const a: T = {};`
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
+ defer done()
+ f.VerifyCodeFix(t, fourslash.VerifyCodeFixOptions{
+ Description: diagnostics.Add_missing_properties.Localize(locale.Default),
+ NewFileContent: `type T = {
+ a: boolean;
+ b: 1;
+ c: -1;
+ d: "a";
+ e: true;
+ f: false;
+ g: bigint;
+ h: -1n;
+ i: null;
+ j: number[];
+ k: { a: string };
+ l: string | number;
+ m: unknown;
+ n: undefined;
+};
+const a: T = {
+ a: false,
+ b: 1,
+ c: -1,
+ d: "a",
+ e: true,
+ f: false,
+ g: 0n,
+ h: -1n,
+ i: null,
+ j: [],
+ k: {
+ a: ""
+ },
+ l: "",
+ m: undefined,
+ n: undefined
+};`,
+ ApplyChanges: true,
+ })
+ f.VerifyNoErrors(t)
+}
diff --git a/tsc/internal/fourslash/tests/codeFixAddMissingProperties5_test.go b/tsc/internal/fourslash/tests/codeFixAddMissingProperties5_test.go
new file mode 100644
index 0000000000000..4964294a6d122
--- /dev/null
+++ b/tsc/internal/fourslash/tests/codeFixAddMissingProperties5_test.go
@@ -0,0 +1,312 @@
+package fourslash_test
+
+import (
+ "strconv"
+ "testing"
+
+ "github.com/microsoft/TypeScript/tsc/internal/diagnostics"
+ "github.com/microsoft/TypeScript/tsc/internal/fourslash"
+ "github.com/microsoft/TypeScript/tsc/internal/locale"
+ "github.com/microsoft/TypeScript/tsc/internal/testutil"
+)
+
+func TestCodeFixAddMissingProperties5(t *testing.T) {
+ t.Parallel()
+ for i, test := range []struct {
+ content string
+ result string
+ filename string
+ }{
+ {
+ content: `enum E { a, b }
+type T = { a: E.b };
+const a: T = {};`,
+ result: `enum E { a, b }
+type T = { a: E.b };
+const a: T = {
+ a: E.b
+};`,
+ },
+ {
+ content: `// @Filename: /a.ts
+export enum E { a, b }
+// @Filename: /b.ts
+import { E as B } from "./a";
+const a: { a: B } = {};`,
+ result: `import { E as B } from "./a";
+const a: { a: B } = {
+ a: B.a
+};`,
+ filename: "/b.ts",
+ },
+ {
+ content: `// @Filename: /a.ts
+export enum E { a, b }
+// @Filename: /b.ts
+import { E as B } from "./a";
+const a: { a: B.b } = {};`,
+ result: `import { E as B } from "./a";
+const a: { a: B.b } = {
+ a: B.b
+};`,
+ filename: "/b.ts",
+ },
+ {
+ content: `// @Filename: /a.ts
+export enum E { a, b }
+// @Filename: /b.ts
+import * as B from "./a";
+const a: { a: B.E } = {};`,
+ result: `import * as B from "./a";
+const a: { a: B.E } = {
+ a: B.E.a
+};`,
+ filename: "/b.ts",
+ },
+ {
+ content: `// @Filename: /a.ts
+export enum E { a, b }
+// @Filename: /b.ts
+import { E as B } from "./a";
+const a: Record = {};`,
+ result: `import { E as B } from "./a";
+const a: Record = {
+ [B.a]: "",
+ [B.b]: ""
+};`,
+ filename: "/b.ts",
+ },
+ {
+ content: `// @Filename: /a.ts
+export class C {}
+// @Filename: /b.ts
+import { C as B } from "./a";
+const a: { a: B } = {};`,
+ result: `import { C as B } from "./a";
+const a: { a: B } = {
+ a: new B
+};`,
+ filename: "/b.ts",
+ },
+ {
+ content: `// @Filename: /a.ts
+export class C {}
+// @Filename: /b.ts
+import * as B from "./a";
+const a: { a: B.C } = {};`,
+ result: `import * as B from "./a";
+const a: { a: B.C } = {
+ a: new B.C
+};`,
+ filename: "/b.ts",
+ },
+ {
+ content: `// @lib: es2015
+// @Filename: /a.ts
+export const a = Symbol();
+// @Filename: /b.ts
+import { a as b } from "./a";
+const a: { [b]: number } = {};`,
+ result: `import { a as b } from "./a";
+const a: { [b]: number } = {
+ [b]: 0
+};`,
+ filename: "/b.ts",
+ },
+ {
+ content: `// @Filename: /a.ts
+export enum E { a, b }
+// @Filename: /b.ts
+import type { E } from "./a";
+const a: { a: E } = {};`,
+ result: `import { E } from "./a";
+const a: { a: E } = {
+ a: E.a
+};`,
+ filename: "/b.ts",
+ },
+ {
+ content: `// @Filename: /a.ts
+export class C {}
+// @Filename: /b.ts
+import { type C as B } from "./a";
+const a: { a: B } = {};`,
+ result: `import { C as B } from "./a";
+const a: { a: B } = {
+ a: new B
+};`,
+ filename: "/b.ts",
+ },
+ {
+ content: `// @Filename: /a.ts
+export enum E { a, b }
+export class C {}
+// @Filename: /b.ts
+import type * as B from "./a";
+const a: { a: B.E; b: B.C } = {};`,
+ result: `import * as B from "./a";
+const a: { a: B.E; b: B.C } = {
+ a: B.E.a,
+ b: new B.C
+};`,
+ filename: "/b.ts",
+ },
+ {
+ content: `// @lib: es2015
+// @Filename: /a.ts
+export const a = Symbol();
+export type T = { [a]: number };
+// @Filename: /b.ts
+import type { T } from "./a";
+const b: T = {};`,
+ result: `import { a, type T } from "./a";
+const b: T = {
+ [a]: 0
+};`,
+ filename: "/b.ts",
+ },
+ {
+ content: `// @lib: es2015
+// @Filename: /a.ts
+export const a = Symbol();
+// @Filename: /b.ts
+import type { a as b } from "./a";
+const a: { [b]: number } = {};`,
+ result: `import { a as b } from "./a";
+const a: { [b]: number } = {
+ [b]: 0
+};`,
+ filename: "/b.ts",
+ },
+ {
+ content: `// @module: esnext
+// @moduleResolution: bundler
+// @verbatimModuleSyntax: true
+// @Filename: /a.ts
+export enum E { a, b }
+export class C {}
+export interface T { a: number }
+// @Filename: /b.ts
+import type { C, E as B, T } from "./a";
+const a: { a: B; b: C; c?: T } = {};`,
+ result: `import { C, E as B, type T } from "./a";
+const a: { a: B; b: C; c?: T } = {
+ a: B.a,
+ b: new C
+};`,
+ filename: "/b.ts",
+ },
+ {
+ content: `// @Filename: /a.ts
+export default class C {}
+// @Filename: /b.ts
+import type B from "./a";
+const a: { a: B } = {};`,
+ result: `import B from "./a";
+const a: { a: B } = {
+ a: new B
+};`,
+ filename: "/b.ts",
+ },
+ {
+ content: `// @Filename: /a.ts
+export enum E { a, b }
+export class C {}
+export type T = { a: E; b: C };
+// @Filename: /b.ts
+import type { T } from "./a";
+const a: T = {};`,
+ result: `import { C, E, type T } from "./a";
+const a: T = {
+ a: E.a,
+ b: new C
+};`,
+ filename: "/b.ts",
+ },
+ {
+ content: `// @Filename: /a.ts
+export class C {}
+export enum E { a, b }
+// @Filename: /b.ts
+import { type /* a */ C as B, type /* b */ E } from "./a";
+const a: { a: B; b: E } = {};`,
+ result: `import { /* a */ C as B, /* b */ E } from "./a";
+const a: { a: B; b: E } = {
+ a: new B,
+ b: E.a
+};`,
+ filename: "/b.ts",
+ },
+ {
+ content: `// @verbatimModuleSyntax: true
+// @Filename: /a.ts
+export class C {}
+export enum E { a, b }
+export interface T { a: number }
+// @Filename: /b.ts
+import type {
+ C, // a
+ E, // b
+ T
+} from "./a";
+const a: { a: C; b: E; c?: T } = {};`,
+ result: `import {
+ C, // a
+ E, // b
+ type T
+} from "./a";
+const a: { a: C; b: E; c?: T } = {
+ a: new C,
+ b: E.a
+};`,
+ filename: "/b.ts",
+ },
+ {
+ content: `// @Filename: /a.ts
+export class C {}
+export enum E { a, b }
+export type T = { a: C; b: E };
+// @Filename: /b.ts
+import type { C as B, T } from "./a";
+const a: T = {};`,
+ result: `import { C as B, E, type T } from "./a";
+const a: T = {
+ a: new B,
+ b: E.a
+};`,
+ filename: "/b.ts",
+ },
+ {
+ content: `// @Filename: /a.ts
+export interface A { a: number }
+export class C {}
+export enum E { a, b }
+export type T = { a: C; b: E; c?: A };
+// @Filename: /b.ts
+import type { A, C, T } from "./a";
+const a: T = {};`,
+ result: `import { type A, C, type T, E } from "./a";
+const a: T = {
+ a: new C,
+ b: E.a
+};`,
+ filename: "/b.ts",
+ },
+ } {
+ t.Run(strconv.Itoa(i+1), func(t *testing.T) {
+ t.Parallel()
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, test.content)
+ defer done()
+ if test.filename != "" {
+ f.GoToFile(t, test.filename)
+ }
+ f.VerifyCodeFix(t, fourslash.VerifyCodeFixOptions{
+ Description: diagnostics.Add_missing_properties.Localize(locale.Default),
+ NewFileContent: test.result,
+ ApplyChanges: true,
+ })
+ f.VerifyNoErrors(t)
+ })
+ }
+}
diff --git a/tsc/internal/fourslash/tests/codeFixAddMissingProperties6_test.go b/tsc/internal/fourslash/tests/codeFixAddMissingProperties6_test.go
new file mode 100644
index 0000000000000..300daceecf8e2
--- /dev/null
+++ b/tsc/internal/fourslash/tests/codeFixAddMissingProperties6_test.go
@@ -0,0 +1,151 @@
+package fourslash_test
+
+import (
+ "strconv"
+ "testing"
+
+ "github.com/microsoft/TypeScript/tsc/internal/diagnostics"
+ "github.com/microsoft/TypeScript/tsc/internal/fourslash"
+ "github.com/microsoft/TypeScript/tsc/internal/locale"
+ "github.com/microsoft/TypeScript/tsc/internal/ls/lsutil"
+ "github.com/microsoft/TypeScript/tsc/internal/testutil"
+)
+
+func TestCodeFixAddMissingProperties6(t *testing.T) {
+ t.Parallel()
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ const content = `// @lib: es2015
+const a = Symbol();
+type T = {
+ [a]: number;
+ "a-b": string;
+ 1: boolean;
+ constructor: number;
+ b: { "a-b": string };
+};
+const b: T = {
+ // a
+ constructor: 1
+};`
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
+ defer done()
+ f.VerifyCodeFix(t, fourslash.VerifyCodeFixOptions{
+ Description: diagnostics.Add_missing_properties.Localize(locale.Default),
+ NewFileContent: `const a = Symbol();
+type T = {
+ [a]: number;
+ "a-b": string;
+ 1: boolean;
+ constructor: number;
+ b: { "a-b": string };
+};
+const b: T = {
+ // a
+ constructor: 1,
+ [a]: 0,
+ 'a-b': '',
+ 1: false,
+ b: {
+ 'a-b': ''
+ }
+};`,
+ UserPreferences: &lsutil.UserPreferences{QuotePreference: "single"},
+ ApplyChanges: true,
+ })
+ f.VerifyNoErrors(t)
+}
+
+func TestCodeFixAddMissingProperties6_1(t *testing.T) {
+ t.Parallel()
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ const content = `class C {
+ "new"(x: number) {}
+}
+const a: C = [|{}|];`
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
+ defer done()
+ f.VerifyCodeFix(t, fourslash.VerifyCodeFixOptions{
+ Description: diagnostics.Add_missing_properties.Localize(locale.Default),
+ NewRangeContent: `{
+ new: function(x: number): void {
+ throw new Error("Function not implemented.");
+ }
+}`,
+ ApplyChanges: true,
+ })
+ f.VerifyNoErrors(t)
+}
+
+func TestCodeFixAddMissingProperties6_2(t *testing.T) {
+ t.Parallel()
+ for i, test := range []struct {
+ declaration string
+ value string
+ }{
+ {`declare class C { constructor(a: number); }`, "undefined"},
+ {`class B { constructor(a: number) {} }
+class C extends B {}`, "undefined"},
+ {`class C { private constructor() {} }`, "undefined"},
+ {`class C { protected constructor() {} }`, "undefined"},
+ {`class C { constructor(a?: number) {} }`, "new C"},
+ {`class C { constructor(a: number = 0) {} }`, "new C"},
+ {`declare class C { constructor(); }`, "new C"},
+ {`class C {
+ constructor();
+ constructor(a: number);
+ constructor(a?: number) {}
+}`, "new C"},
+ {`class C { constructor(...a: number[]) {} }`, "new C"},
+ {`class C { constructor(...a: [number]) {} }`, "undefined"},
+ {`class C { constructor(...a: [number?]) {} }`, "new C"},
+ {`abstract class C {}`, "undefined"},
+ {`class B { protected constructor() {} }
+class C extends B {}`, "undefined"},
+ } {
+ t.Run(strconv.Itoa(i+1), func(t *testing.T) {
+ t.Parallel()
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ content := test.declaration + "\nconst a: { a: C } = [|{}|];"
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
+ defer done()
+ f.VerifyCodeFix(t, fourslash.VerifyCodeFixOptions{
+ Description: diagnostics.Add_missing_properties.Localize(locale.Default),
+ NewRangeContent: "{\n a: " + test.value + "\n}",
+ ApplyChanges: true,
+ })
+ if test.value == "undefined" {
+ f.VerifyNumberOfErrorsInCurrentFile(t, 1)
+ } else {
+ f.VerifyNoErrors(t)
+ }
+ })
+ }
+}
+
+func TestCodeFixAddMissingProperties6_3(t *testing.T) {
+ t.Parallel()
+ for i, declaration := range []string{
+ `class C {
+ private constructor() {}
+ static a() {`,
+ `class C { protected constructor() {} }
+class B extends C {
+ static a() {`,
+ } {
+ t.Run(strconv.Itoa(i+1), func(t *testing.T) {
+ t.Parallel()
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ content := declaration + "\nconst a: { a: C } = [|{}|];\nreturn a;\n }\n}"
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
+ defer done()
+ f.VerifyCodeFix(t, fourslash.VerifyCodeFixOptions{
+ Description: diagnostics.Add_missing_properties.Localize(locale.Default),
+ NewRangeContent: `{
+ a: new C
+}`,
+ ApplyChanges: true,
+ })
+ f.VerifyNoErrors(t)
+ })
+ }
+}
diff --git a/tsc/internal/fourslash/tests/codeFixAddMissingProperties7_test.go b/tsc/internal/fourslash/tests/codeFixAddMissingProperties7_test.go
new file mode 100644
index 0000000000000..24dd0c7ad64a5
--- /dev/null
+++ b/tsc/internal/fourslash/tests/codeFixAddMissingProperties7_test.go
@@ -0,0 +1,51 @@
+package fourslash_test
+
+import (
+ "testing"
+
+ "github.com/microsoft/TypeScript/tsc/internal/diagnostics"
+ "github.com/microsoft/TypeScript/tsc/internal/fourslash"
+ "github.com/microsoft/TypeScript/tsc/internal/locale"
+ "github.com/microsoft/TypeScript/tsc/internal/testutil"
+)
+
+func TestCodeFixAddMissingProperties7(t *testing.T) {
+ t.Parallel()
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ const content = `type T = { a: T };
+const a: T = {};`
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
+ defer done()
+ f.VerifyCodeFix(t, fourslash.VerifyCodeFixOptions{
+ Description: diagnostics.Add_missing_properties.Localize(locale.Default),
+ NewFileContent: `type T = { a: T };
+const a: T = {
+ a: {
+ a: undefined
+ }
+};`,
+ })
+}
+
+func TestCodeFixAddMissingProperties7_1(t *testing.T) {
+ t.Parallel()
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ const content = `type T = { a: T };
+const a: T = {};`
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
+ defer done()
+ f.VerifyCodeFix(t, fourslash.VerifyCodeFixOptions{
+ Description: diagnostics.Add_missing_properties.Localize(locale.Default),
+ NewFileContent: `type T = { a: T };
+const a: T = {
+ a: {
+ a: {
+ a: {
+ a: undefined
+ }
+ }
+ }
+};`,
+ ApplyChanges: true,
+ })
+}
diff --git a/tsc/internal/fourslash/tests/codeFixAddMissingProperties8_test.go b/tsc/internal/fourslash/tests/codeFixAddMissingProperties8_test.go
new file mode 100644
index 0000000000000..0b6391a310807
--- /dev/null
+++ b/tsc/internal/fourslash/tests/codeFixAddMissingProperties8_test.go
@@ -0,0 +1,42 @@
+package fourslash_test
+
+import (
+ "strconv"
+ "testing"
+
+ "github.com/microsoft/TypeScript/tsc/internal/diagnostics"
+ "github.com/microsoft/TypeScript/tsc/internal/fourslash"
+ "github.com/microsoft/TypeScript/tsc/internal/locale"
+ "github.com/microsoft/TypeScript/tsc/internal/testutil"
+)
+
+func TestCodeFixAddMissingProperties8(t *testing.T) {
+ t.Parallel()
+ for i, test := range []struct {
+ content string
+ errors int
+ }{
+ {`declare function a(b: { c: number }): void;
+a({ c: "" });`, 1},
+ {`const a = {};
+const b: { c: number } = a;`, 1},
+ {`const a = {};
+a.b;`, 1},
+ {`const a: { b?: number } = {};`, 0},
+ {`declare function a({ b }: { b: number }): void;
+a({});`, 1},
+ {`declare function a(...[b]: { c: number }[]): void;
+a({ c: 1 }, {});`, 1},
+ {`declare function a(b: number): void;
+a(1, {});`, 1},
+ } {
+ t.Run(strconv.Itoa(i+1), func(t *testing.T) {
+ t.Parallel()
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, test.content)
+ defer done()
+ f.VerifyNumberOfErrorsInCurrentFile(t, test.errors)
+ f.VerifyCodeFixNotAvailable(t, diagnostics.Add_missing_properties.Localize(locale.Default))
+ })
+ }
+}
diff --git a/tsc/internal/fourslash/tests/codeFixAddMissingProperties9_test.go b/tsc/internal/fourslash/tests/codeFixAddMissingProperties9_test.go
new file mode 100644
index 0000000000000..04ee942c13023
--- /dev/null
+++ b/tsc/internal/fourslash/tests/codeFixAddMissingProperties9_test.go
@@ -0,0 +1,51 @@
+package fourslash_test
+
+import (
+ "testing"
+
+ "github.com/microsoft/TypeScript/tsc/internal/diagnostics"
+ "github.com/microsoft/TypeScript/tsc/internal/fourslash"
+ "github.com/microsoft/TypeScript/tsc/internal/locale"
+ "github.com/microsoft/TypeScript/tsc/internal/testutil"
+)
+
+func TestCodeFixAddMissingProperties9(t *testing.T) {
+ t.Parallel()
+ defer testutil.RecoverAndFail(t, "Panic on fourslash test")
+ const content = `// @strict: true
+type T = { a: U };
+const a: T>>>>>>>>>>> = {};`
+ f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
+ defer done()
+ f.VerifyCodeFix(t, fourslash.VerifyCodeFixOptions{
+ Description: diagnostics.Add_missing_properties.Localize(locale.Default),
+ NewFileContent: `type T = { a: U };
+const a: T>>>>>>>>>>> = {
+ a: {
+ a: {
+ a: {
+ a: {
+ a: {
+ a: {
+ a: {
+ a: {
+ a: {
+ a: {
+ a: {
+ a: 0
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+};`,
+ ApplyChanges: true,
+ })
+ f.VerifyNoErrors(t)
+}
diff --git a/tsc/internal/ls/autoimport/fix.go b/tsc/internal/ls/autoimport/fix.go
index 76d8498e6fc7b..7739e2845879e 100644
--- a/tsc/internal/ls/autoimport/fix.go
+++ b/tsc/internal/ls/autoimport/fix.go
@@ -73,7 +73,7 @@ func (f *Fix) Edits(
panic("import index out of range")
}
existingFix := getAddToExistingImportFix(file, f)
- addToExistingImport(tracker, file, existingFix.importClauseOrBindingPattern, existingFix.defaultImport, core.SingleElementSlice(existingFix.namedImport), preferences)
+ addToExistingImport(tracker, file, existingFix.importClauseOrBindingPattern, existingFix.defaultImport, core.SingleElementSlice(existingFix.namedImport), collections.Set[*ast.Node]{}, compilerOptions, preferences)
edits, safe := fileEdits(tracker, file)
return edits, diagnostics.Update_import_from_0.Localize(locale, f.ModuleSpecifier), safe
case lsproto.AutoImportFixKindAddNew:
@@ -201,6 +201,8 @@ func addToExistingImport(
importClauseOrBindingPattern *ast.Node,
defaultImport *newImportBinding,
namedImports []*newImportBinding,
+ importsToPromote collections.Set[*ast.Node],
+ compilerOptions *core.CompilerOptions,
preferences lsutil.UserPreferences,
) {
switch importClauseOrBindingPattern.Kind {
@@ -217,12 +219,13 @@ func addToExistingImport(
importClause := importClauseOrBindingPattern.AsImportClause()
// promoteFromTypeOnly = true if we need to promote the entire original clause from type only
- promoteFromTypeOnly := importClause.IsTypeOnly() && core.Some(append(namedImports, defaultImport), func(i *newImportBinding) bool {
+ promoteFromTypeOnly := importClause.IsTypeOnly() && (importsToPromote.Len() > 0 || core.Some(append(namedImports, defaultImport), func(i *newImportBinding) bool {
if i == nil {
return false
}
return i.addAsTypeOnly == lsproto.AddAsTypeOnlyNotAllowed
- })
+ }))
+ preserveExistingTypeOnly := promoteFromTypeOnly && (importsToPromote.Len() == 0 || len(namedImports) > 0 || compilerOptions.VerbatimModuleSyntax.IsTrue())
var existingSpecifiers []*ast.Node
if importClause.NamedBindings != nil && importClause.NamedBindings.Kind == ast.KindNamedImports {
@@ -241,14 +244,10 @@ func addToExistingImport(
if namedImport.propertyName != "" {
identifier = ct.NodeFactory.NewIdentifier(namedImport.propertyName).AsIdentifier().AsNode()
}
- return ct.NodeFactory.NewImportSpecifier(
- (!importClause.IsTypeOnly() || promoteFromTypeOnly) && shouldUseTypeOnly(namedImport.addAsTypeOnly, preferences),
- identifier,
- ct.NodeFactory.NewIdentifier(namedImport.name),
- )
+ return ct.NodeFactory.NewImportSpecifier((!importClause.IsTypeOnly() || promoteFromTypeOnly) && shouldUseTypeOnly(namedImport.addAsTypeOnly, preferences), identifier, ct.NodeFactory.NewIdentifier(namedImport.name))
})
slices.SortFunc(newSpecifiers, specifierComparer)
- if len(existingSpecifiers) > 0 && isSorted != core.TSFalse {
+ if len(existingSpecifiers) > 0 {
// The sorting preference computed earlier may or may not have validated that these particular
// import specifiers are sorted. If they aren't, `getImportSpecifierInsertionIndex` will return
// nonsense. So if there are existing specifiers, even if we know the sorting preference, we
@@ -258,35 +257,27 @@ func addToExistingImport(
// If we're promoting the clause from type-only, we need to transform the existing imports
// before attempting to insert the new named imports (for comparison purposes only)
specsToCompareAgainst := existingSpecifiers
- if promoteFromTypeOnly && len(existingSpecifiers) > 0 {
+ if promoteFromTypeOnly || importsToPromote.Len() > 0 {
specsToCompareAgainst = core.Map(existingSpecifiers, func(e *ast.Node) *ast.Node {
spec := e.AsImportSpecifier()
- var propertyName *ast.Node
- if spec.PropertyName != nil {
- propertyName = spec.PropertyName
- }
- syntheticSpec := ct.NodeFactory.NewImportSpecifier(
- true, // isTypeOnly
- propertyName,
- spec.Name(),
- )
- return syntheticSpec
+ return ct.NodeFactory.NewImportSpecifier(!importsToPromote.Has(e) && (spec.IsTypeOnly || preserveExistingTypeOnly), spec.PropertyName, spec.Name())
})
}
+ canInsertSorted := isSorted != core.TSFalse && slices.IsSortedFunc(specsToCompareAgainst, specifierComparer)
for _, spec := range newSpecifiers {
- insertionIndex := lsutil.GetImportSpecifierInsertionIndex(specsToCompareAgainst, spec, specifierComparer)
- ct.InsertImportSpecifierAtIndex(file, spec, importClause.NamedBindings, insertionIndex)
- }
- } else if len(existingSpecifiers) > 0 {
- for _, spec := range newSpecifiers {
- ct.InsertNodeInListAfter(file, existingSpecifiers[len(existingSpecifiers)-1], spec.AsNode(), nil)
+ if canInsertSorted {
+ insertionIndex := lsutil.GetImportSpecifierInsertionIndex(specsToCompareAgainst, spec, specifierComparer)
+ ct.InsertImportSpecifierAtIndex(file, spec, importClause.NamedBindings, insertionIndex)
+ } else {
+ ct.InsertNodeInListAfter(file, existingSpecifiers[len(existingSpecifiers)-1], spec.AsNode(), nil /*containingList*/)
+ }
}
} else {
if len(newSpecifiers) > 0 {
namedImports := ct.NodeFactory.NewNamedImports(ct.NodeFactory.NewNodeList(newSpecifiers))
if importClause.NamedBindings != nil {
- ct.ReplaceNode(file, importClause.NamedBindings, namedImports, nil)
+ ct.ReplaceNode(file, importClause.NamedBindings, namedImports, nil /*options*/)
} else {
if importClause.Name() == nil {
panic("Import clause must have either named imports or a default import")
@@ -301,18 +292,18 @@ func addToExistingImport(
// Delete the 'type' keyword from the import clause
typeKeyword := getTypeKeywordOfTypeOnlyImport(importClause, file)
ct.Delete(file, typeKeyword)
-
- // Add 'type' modifier to existing specifiers (not newly added ones)
- // We preserve the type-onlyness of existing specifiers regardless of whether
- // it would make a difference in emit (user preference).
- if len(existingSpecifiers) > 0 {
- for _, specifier := range existingSpecifiers {
- if !specifier.AsImportSpecifier().IsTypeOnly {
- ct.InsertModifierBefore(file, ast.KindTypeKeyword, specifier)
- }
+ }
+ for _, specifier := range existingSpecifiers {
+ if importsToPromote.Has(specifier) {
+ if specifier.AsImportSpecifier().IsTypeOnly {
+ deleteTypeKeyword(ct, file, specifier.Pos())
}
+ } else if preserveExistingTypeOnly && !specifier.AsImportSpecifier().IsTypeOnly {
+ ct.InsertModifierBefore(file, ast.KindTypeKeyword, specifier)
}
}
+ case ast.KindImportEqualsDeclaration:
+ promoteFromTypeOnly(ct, importClauseOrBindingPattern, compilerOptions, file, preferences)
default:
panic("Unsupported clause kind: " + importClauseOrBindingPattern.KindString() + " for addToExistingImport")
}
diff --git a/tsc/internal/ls/autoimport/import_adder.go b/tsc/internal/ls/autoimport/import_adder.go
index facc01393ad0b..6c663c78fc406 100644
--- a/tsc/internal/ls/autoimport/import_adder.go
+++ b/tsc/internal/ls/autoimport/import_adder.go
@@ -8,6 +8,7 @@ import (
"github.com/microsoft/TypeScript/tsc/internal/ast"
"github.com/microsoft/TypeScript/tsc/internal/checker"
+ "github.com/microsoft/TypeScript/tsc/internal/collections"
"github.com/microsoft/TypeScript/tsc/internal/compiler"
"github.com/microsoft/TypeScript/tsc/internal/core"
"github.com/microsoft/TypeScript/tsc/internal/debug"
@@ -25,6 +26,7 @@ type ImportAdder interface {
HasFixes() bool
AddImportFromExportedSymbol(symbol *ast.Symbol, isValidTypeOnlyUseSite bool)
AddImportFix(fix *Fix)
+ WriteFixes(tracker *change.Tracker)
Edits() []*lsproto.TextEdit
}
@@ -33,6 +35,7 @@ type addToExistingState struct {
importClauseOrBindingPattern *ast.ImportClauseOrBindingPattern
defaultImport *newImportBinding
namedImports map[string]*newImportBinding
+ importsToPromote collections.Set[*ast.Node]
}
// importsCollection tracks new imports to be created for a given module specifier
@@ -116,8 +119,14 @@ func (adder *importAdder) AddImportFromExportedSymbol(exportedSymbol *ast.Symbol
}
func (adder *importAdder) Edits() []*lsproto.TextEdit {
- // !!! organize imports?
tracker := change.NewTracker(adder.ctx, adder.view.program.Options(), adder.formatOptions, adder.converters)
+ adder.WriteFixes(tracker)
+ changes, _ := tracker.GetChanges()
+ return changes[adder.view.importingFile.OriginalFileName()]
+}
+
+func (adder *importAdder) WriteFixes(tracker *change.Tracker) {
+ // !!! organize imports?
quotePreference := lsutil.GetQuotePreference(adder.view.importingFile, adder.preferences)
for _, fix := range adder.addToNamespace {
addNamespaceQualifier(fix, tracker, adder.view.importingFile, locale.Default)
@@ -132,6 +141,8 @@ func (adder *importAdder) Edits() []*lsproto.TextEdit {
clauseOrPattern,
entry.defaultImport,
sortedNamedImports(entry.namedImports),
+ entry.importsToPromote,
+ adder.view.program.Options(),
adder.preferences,
)
}
@@ -168,11 +179,6 @@ func (adder *importAdder) Edits() []*lsproto.TextEdit {
if len(newDeclarations) > 0 {
insertImports(tracker, adder.view.importingFile, newDeclarations, true /*blankLineBetween*/, adder.preferences)
}
-
- // Unmappable files are dropped by GetChanges, so a content-mapped importing file that cannot be
- // faithfully rewritten yields no edits rather than a corrupting one.
- changes, _ := tracker.GetChanges()
- return changes[adder.view.importingFile.OriginalFileName()]
}
func sortedNamedImports(m map[string]*newImportBinding) []*newImportBinding {
@@ -197,14 +203,7 @@ func (adder *importAdder) AddImportFix(fix *Fix) {
adder.importType = append(adder.importType, fix)
case lsproto.AutoImportFixKindAddToExisting:
existingFix := getAddToExistingImportFix(adder.view.importingFile, fix)
- entry := adder.addToExisting[existingFix.importClauseOrBindingPattern]
- if entry == nil {
- entry = &addToExistingState{
- importClauseOrBindingPattern: existingFix.importClauseOrBindingPattern,
- namedImports: make(map[string]*newImportBinding),
- }
- adder.addToExisting[existingFix.importClauseOrBindingPattern] = entry
- }
+ entry := adder.getAddToExistingEntry(existingFix.importClauseOrBindingPattern)
if fix.ImportKind == lsproto.ImportKindNamed {
prevImport := entry.namedImports[symbolName]
@@ -315,12 +314,33 @@ func (adder *importAdder) AddImportFix(fix *Fix) {
}
case lsproto.AutoImportFixKindPromoteTypeOnly:
- // Excluding from fix-all
+ declaration := fix.TypeOnlyAliasDeclaration
+ clause := declaration
+ switch declaration.Kind {
+ case ast.KindImportSpecifier:
+ clause = declaration.Parent.Parent
+ case ast.KindNamespaceImport:
+ clause = declaration.Parent
+ }
+ entry := adder.getAddToExistingEntry(clause)
+ entry.importsToPromote.Add(declaration)
default:
debug.Fail(fmt.Sprintf("Unexpected fix kind: %v", fix.Kind))
}
}
+func (adder *importAdder) getAddToExistingEntry(clause *ast.Node) *addToExistingState {
+ entry := adder.addToExisting[clause]
+ if entry == nil {
+ entry = &addToExistingState{
+ importClauseOrBindingPattern: clause,
+ namedImports: make(map[string]*newImportBinding),
+ }
+ adder.addToExisting[clause] = entry
+ }
+ return entry
+}
+
// `NotAllowed` overrides `Required` because one addition of a new import might be required to be type-only
// because of `--importsNotUsedAsValues=error`, but if a second addition of the same import is `NotAllowed`
// to be type-only, the reason the first one was `Required` - the unused runtime dependency - is now moot.
diff --git a/tsc/internal/ls/change/tracker.go b/tsc/internal/ls/change/tracker.go
index d797d2cc821f4..0341c2284737a 100644
--- a/tsc/internal/ls/change/tracker.go
+++ b/tsc/internal/ls/change/tracker.go
@@ -27,7 +27,7 @@ type NodeOptions struct {
Suffix string
// Text of inserted node will be formatted with this indentation, otherwise indentation will be inferred from the old node
- indentation *int
+ Indentation *int
// Text of inserted node will be formatted with this delta, otherwise delta will be inferred from the new node kind
delta *int
@@ -419,6 +419,13 @@ func (t *Tracker) endPosForInsertNodeAfter(sourceFile *ast.SourceFile, after *as
* Note that separators are part of the node in statements and class elements.
*/
func (t *Tracker) InsertNodeInListAfter(sourceFile *ast.SourceFile, after *ast.Node, newNode *ast.Node, containingList *ast.NodeList) {
+ t.InsertNodesInListAfter(sourceFile, after, []*ast.Node{newNode}, containingList)
+}
+
+func (t *Tracker) InsertNodesInListAfter(sourceFile *ast.SourceFile, after *ast.Node, newNodes []*ast.Node, containingList *ast.NodeList) {
+ if len(newNodes) == 0 {
+ return
+ }
if containingList == nil {
containingList = format.GetContainingList(after, sourceFile)
}
@@ -456,7 +463,7 @@ func (t *Tracker) InsertNodeInListAfter(sourceFile *ast.SourceFile, after *ast.N
// write separator and leading trivia of the next element as suffix
suffix := scanner.TokenToString(nextToken.Kind) + sourceFile.Text()[nextToken.End():startPos]
- t.InsertNodesAt(sourceFile, core.TextPos(startPos), []*ast.Node{newNode}, NodeOptions{Suffix: suffix})
+ t.InsertNodesAt(sourceFile, core.TextPos(startPos), newNodes, NodeOptions{Suffix: suffix, joiner: scanner.TokenToString(nextToken.Kind) + t.newLine})
}
return
}
@@ -486,14 +493,14 @@ func (t *Tracker) InsertNodeInListAfter(sourceFile *ast.SourceFile, after *ast.N
// in this case we'll always treat containing list as multiline
multilineList = true
}
+ separatorString := scanner.TokenToString(separator)
+ suffix := ""
+ nextToken := astnav.GetTokenAtPosition(sourceFile, end)
+ if isSeparator(after, nextToken) {
+ end = nextToken.End()
+ suffix = separatorString
+ }
if multilineList {
- // insert separator immediately following the 'after' node to preserve comments in trailing trivia
- separatorToken := t.NewToken(separator)
- separatorString := scanner.TokenToString(separator)
- separatorToken.Loc = core.NewTextRange(end, end+len(separatorString))
- separatorToken.Parent = after.Parent
- endPos := core.TextPos(end)
- t.ReplaceRange(sourceFile, core.NewTextRange(int(endPos), int(endPos)), separatorToken, NodeOptions{})
// use the same indentation as 'after' item
indentation := format.FindFirstNonWhitespaceColumn(afterStartLinePosition, afterStart, sourceFile, t.formatSettings)
// insert element before the line break on the line that contains 'after' element
@@ -502,20 +509,32 @@ func (t *Tracker) InsertNodeInListAfter(sourceFile *ast.SourceFile, after *ast.N
for insertPos != end && stringutil.IsLineBreak(rune(sourceFile.Text()[insertPos-1])) {
insertPos--
}
+ prefix := t.newLine
+ if suffix == "" {
+ if insertPos == end {
+ prefix = separatorString + prefix
+ } else {
+ t.insertTextAt(sourceFile, core.TextPos(end), separatorString)
+ }
+ }
insertLSPos := core.TextPos(insertPos)
- t.ReplaceRange(
+ t.ReplaceRangeWithNodes(
sourceFile,
core.NewTextRange(int(insertLSPos), int(insertLSPos)),
- newNode,
+ newNodes,
NodeOptions{
- indentation: &indentation,
- Prefix: t.newLine,
+ Indentation: &indentation,
+ Prefix: prefix,
+ Suffix: suffix,
+ joiner: separatorString + t.newLine,
},
)
} else {
- separatorString := scanner.TokenToString(separator)
- endPos := core.TextPos(end)
- t.ReplaceRange(sourceFile, core.NewTextRange(int(endPos), int(endPos)), newNode, NodeOptions{Prefix: separatorString + " "})
+ prefix := separatorString + " "
+ if suffix != "" {
+ prefix = " "
+ }
+ t.InsertNodesAt(sourceFile, core.TextPos(end), newNodes, NodeOptions{Prefix: prefix, Suffix: suffix, joiner: separatorString + " "})
}
}
@@ -753,7 +772,7 @@ func (t *Tracker) getInsertNodeAtStartInsertOptions(sourceFile *ast.SourceFile,
prefix = "," + prefix
}
- return NodeOptions{indentation: &indentation, Prefix: prefix, Suffix: suffix}
+ return NodeOptions{Indentation: &indentation, Prefix: prefix, Suffix: suffix}
}
func (t *Tracker) finishNodesWithInsertionsAtStart() {
diff --git a/tsc/internal/ls/change/trackerimpl.go b/tsc/internal/ls/change/trackerimpl.go
index 548bcc4d1d1eb..2e6406486eec8 100644
--- a/tsc/internal/ls/change/trackerimpl.go
+++ b/tsc/internal/ls/change/trackerimpl.go
@@ -145,7 +145,13 @@ func (t *Tracker) computeNewText(change *trackerEdit, targetSourceFile *ast.Sour
if joiner == "" {
joiner = t.newLine
}
- text = strings.Join(core.Map(change.nodes, func(n *ast.Node) string { return strings.TrimSuffix(formatNode(n), t.newLine) }), joiner)
+ text = strings.Join(core.Map(change.nodes, func(n *ast.Node) string {
+ nodeText := strings.TrimSuffix(formatNode(n), t.newLine)
+ if !strings.ContainsAny(joiner, "\r\n") {
+ nodeText = strings.TrimLeftFunc(nodeText, unicode.IsSpace)
+ }
+ return nodeText
+ }), joiner)
case trackerEditKindReplaceWithSingleNode:
text = formatNode(change.Node)
default:
@@ -153,7 +159,7 @@ func (t *Tracker) computeNewText(change *trackerEdit, targetSourceFile *ast.Sour
}
// Strip initial indentation if text will be inserted in the middle of the line.
noIndent := text
- if !(change.options.indentation != nil || format.GetLineStartPositionForPosition(pos, projection) == pos) {
+ if !(change.options.Indentation != nil || format.GetLineStartPositionForPosition(pos, projection) == pos) {
noIndent = strings.TrimLeftFunc(text, unicode.IsSpace)
}
candidate := change.options.Prefix + noIndent + core.IfElse(strings.HasSuffix(noIndent, change.options.Suffix), "", change.options.Suffix)
@@ -184,7 +190,7 @@ func (t *Tracker) computeNewText(change *trackerEdit, targetSourceFile *ast.Sour
// content-mapped file whose projection is indented differently from the document the edit is applied to.
// Edits carrying an explicit indentation option are left alone.
func (t *Tracker) reindentInsertedLines(sourceFile *ast.SourceFile, change *trackerEdit, text string) string {
- if text == "" || change.TextRange.Pos() != change.TextRange.End() || change.options.indentation != nil {
+ if text == "" || change.TextRange.Pos() != change.TextRange.End() || change.options.Indentation != nil {
return text
}
if !strings.HasSuffix(text, t.newLine) {
@@ -234,10 +240,10 @@ func (t *Tracker) getFormattedTextOfNode(nodeIn *ast.Node, targetSourceFile *ast
formatOptions := GetFormatCodeSettingsForWriting(t.formatSettings, targetSourceFile)
var initialIndentation, delta int
- if options.indentation == nil {
+ if options.Indentation == nil {
initialIndentation = format.GetIndentation(pos, sourceFile, formatOptions, options.Prefix == t.newLine || format.GetLineStartPositionForPosition(pos, sourceFile) == pos)
} else {
- initialIndentation = *options.indentation
+ initialIndentation = *options.Indentation
}
if options.delta != nil {
diff --git a/tsc/internal/ls/codeactions.go b/tsc/internal/ls/codeactions.go
index a95b8bbc9eb60..98fb967d82665 100644
--- a/tsc/internal/ls/codeactions.go
+++ b/tsc/internal/ls/codeactions.go
@@ -7,11 +7,14 @@ import (
"strings"
"github.com/microsoft/TypeScript/tsc/internal/ast"
+ "github.com/microsoft/TypeScript/tsc/internal/checker"
"github.com/microsoft/TypeScript/tsc/internal/collections"
"github.com/microsoft/TypeScript/tsc/internal/compiler"
"github.com/microsoft/TypeScript/tsc/internal/core"
"github.com/microsoft/TypeScript/tsc/internal/diagnostics"
"github.com/microsoft/TypeScript/tsc/internal/locale"
+ "github.com/microsoft/TypeScript/tsc/internal/ls/autoimport"
+ "github.com/microsoft/TypeScript/tsc/internal/ls/change"
"github.com/microsoft/TypeScript/tsc/internal/ls/lsconv"
"github.com/microsoft/TypeScript/tsc/internal/lsp/lsproto"
"github.com/microsoft/TypeScript/tsc/internal/spanmap"
@@ -22,7 +25,7 @@ type CodeFixProvider struct {
ErrorCodes []int32
GetCodeActions func(ctx context.Context, fixContext *CodeFixContext) ([]*CodeAction, error)
FixIds []string
- GetAllCodeActions func(ctx context.Context, fixContext *CodeFixContext) (*CombinedCodeActions, error)
+ GetAllCodeActions func(ctx context.Context, fixContext *CodeFixContext, fixAll *CodeFixAll) (*CombinedCodeActions, error)
}
// CodeFixContext contains the context needed to generate code fixes
@@ -36,6 +39,33 @@ type CodeFixContext struct {
Params *lsproto.CodeActionParams
}
+type CodeFixAll struct {
+ diagnostics []*ast.Diagnostic
+ typeChecker *checker.Checker
+ importAdder autoimport.ImportAdder
+}
+
+func (fixAll *CodeFixAll) getImportAdder(ctx context.Context, fixContext *CodeFixContext) (autoimport.ImportAdder, error) {
+ if fixAll.importAdder == nil {
+ importAdder, err := fixContext.LS.createImportAdder(ctx, fixAll.typeChecker, fixContext.SourceFile)
+ if err == nil {
+ fixAll.importAdder = importAdder
+ } else {
+ return nil, err
+ }
+ }
+ return fixAll.importAdder, nil
+}
+
+func getCodeFixChanges(sourceFile *ast.SourceFile, tracker *change.Tracker) []*lsproto.TextEdit {
+ changes, unmappable := tracker.GetChanges()
+ if len(unmappable) != 0 {
+ return nil
+ }
+
+ return changes[sourceFile.OriginalFileName()]
+}
+
// CodeAction represents a single code action fix
type CodeAction struct {
Description string
@@ -72,6 +102,8 @@ var codeFixProviders = []*CodeFixProvider{
ImportFixProvider,
IsolatedDeclarationsFixProvider,
FixClassIncorrectlyImplementsInterfaceProvider,
+ AddMissingPropertiesFixProvider,
+ AddMissingJsxAttributesFixProvider,
// Add more code fix providers here as they are implemented
}
@@ -168,6 +200,10 @@ func (l *LanguageService) getFixAllQuickFixes(
fixIdSeen map[string]*CodeFixProvider,
) ([]lsproto.CommandOrCodeAction, error) {
var actions []lsproto.CommandOrCodeAction
+ if len(fixIdSeen) == 0 {
+ return nil, nil
+ }
+ allDiagnostics := getAllDiagnostics(ctx, program, file)
// Deduplicate providers; multiple fixIds may map to the same provider.
var seen collections.Set[*CodeFixProvider]
@@ -181,16 +217,11 @@ func (l *LanguageService) getFixAllQuickFixes(
continue
}
- if !hasMultipleFixableDiagnostics(ctx, program, file, provider.ErrorCodes) {
+ if !hasMultipleFixableDiagnostics(allDiagnostics, provider.ErrorCodes) {
continue
}
- fixContext := &CodeFixContext{
- SourceFile: file,
- Program: program,
- LS: l,
- }
- combined, err := provider.GetAllCodeActions(ctx, fixContext)
+ combined, err := l.getCombinedCodeActions(ctx, program, file, []*CodeFixProvider{provider}, allDiagnostics)
if err != nil {
return nil, err
}
@@ -215,8 +246,7 @@ func (l *LanguageService) getFixAllQuickFixes(
// hasMultipleFixableDiagnostics returns true if the file has at least 2 diagnostics
// matching the given error codes. Checks all diagnostic sources (semantic,
// syntactic, suggestion, declaration) to match ProvideDiagnostics.
-func hasMultipleFixableDiagnostics(ctx context.Context, program *compiler.Program, file *ast.SourceFile, errorCodes []int32) bool {
- allDiags := getAllDiagnostics(ctx, program, file)
+func hasMultipleFixableDiagnostics(allDiags []*ast.Diagnostic, errorCodes []int32) bool {
count := 0
for _, d := range allDiags {
if isFixableDiagnostic(d, errorCodes) {
@@ -268,38 +298,97 @@ func (l *LanguageService) createFixAllAction(
uri lsproto.DocumentUri,
) (*lsproto.CommandOrCodeAction, error) {
kind := lsproto.CodeActionKindSourceFixAllTs
- lspChanges := make(map[lsproto.DocumentUri][]*lsproto.TextEdit)
+ combined, err := l.getCombinedCodeActions(ctx, program, file, codeFixProviders, getAllDiagnostics(ctx, program, file))
+ if err != nil {
+ return nil, err
+ }
+ if combined == nil {
+ return nil, nil
+ }
+ lspChanges := map[lsproto.DocumentUri][]*lsproto.TextEdit{uri: combined.Changes}
- for _, provider := range codeFixProviders {
- if provider.GetAllCodeActions == nil {
- continue
+ return &lsproto.CommandOrCodeAction{
+ CodeAction: &lsproto.CodeAction{
+ Title: diagnostics.Fix_All.Localize(locale.FromContext(ctx)),
+ Kind: &kind,
+ Edit: &lsproto.WorkspaceEdit{Changes: &lspChanges},
+ },
+ }, nil
+}
+
+func (l *LanguageService) getCombinedCodeActions(ctx context.Context, program *compiler.Program, file *ast.SourceFile, providers []*CodeFixProvider, allDiagnostics []*ast.Diagnostic) (*CombinedCodeActions, error) {
+ if len(file.SupplementalSourceFiles()) == 0 {
+ return l.getCombinedCodeActionsForFile(ctx, program, file, providers, allDiagnostics)
+ }
+
+ tracker := change.NewTracker(ctx, program.Options(), l.FormatOptions(), l.converters)
+ var description string
+ files := append([]*ast.SourceFile{file}, file.SupplementalSourceFiles()...)
+ for _, sourceFile := range files {
+ combined, err := l.getCombinedCodeActionsForFile(ctx, program, sourceFile, providers, allDiagnostics)
+ if err != nil {
+ return nil, err
+ }
+ if combined != nil {
+ description = combined.Description
+ for _, edit := range combined.Changes {
+ tracker.ReplaceRangeWithText(sourceFile, edit.Range, edit.NewText)
+ }
}
+ }
+
+ changes := getCodeFixChanges(file, tracker)
+ if len(changes) == 0 {
+ return nil, nil
+ }
+ return &CombinedCodeActions{Description: description, Changes: changes}, nil
+}
+
+func (l *LanguageService) getCombinedCodeActionsForFile(ctx context.Context, program *compiler.Program, file *ast.SourceFile, providers []*CodeFixProvider, allDiagnostics []*ast.Diagnostic) (*CombinedCodeActions, error) {
+ typeChecker, done := program.GetTypeCheckerForFile(ctx, file)
+ defer done()
+ fixContext := &CodeFixContext{
+ SourceFile: file,
+ Program: program,
+ LS: l,
+ }
+ fixAll := &CodeFixAll{
+ diagnostics: allDiagnostics,
+ typeChecker: typeChecker,
+ }
- fixContext := &CodeFixContext{
- SourceFile: file,
- Program: program,
- LS: l,
+ var description string
+ var edits []*lsproto.TextEdit
+ for _, provider := range providers {
+ if provider.GetAllCodeActions == nil {
+ continue
}
- combined, err := provider.GetAllCodeActions(ctx, fixContext)
+ combined, err := provider.GetAllCodeActions(ctx, fixContext, fixAll)
if err != nil {
return nil, err
}
- if combined != nil && len(combined.Changes) > 0 {
- lspChanges[uri] = append(lspChanges[uri], combined.Changes...)
+ if combined != nil {
+ description = combined.Description
+ edits = append(edits, combined.Changes...)
}
}
- if len(lspChanges) == 0 {
+ if fixAll.importAdder != nil && fixAll.importAdder.HasFixes() {
+ tracker := change.NewTracker(ctx, program.Options(), l.FormatOptions(), l.converters)
+ fixAll.importAdder.WriteFixes(tracker)
+ changes, unmappable := tracker.GetChanges()
+ if len(unmappable) != 0 {
+ return nil, nil
+ }
+ edits = append(edits, changes[file.OriginalFileName()]...)
+ }
+ if len(edits) == 0 {
return nil, nil
}
-
- return &lsproto.CommandOrCodeAction{
- CodeAction: &lsproto.CodeAction{
- Title: diagnostics.Fix_All.Localize(locale.FromContext(ctx)),
- Kind: &kind,
- Edit: &lsproto.WorkspaceEdit{Changes: &lspChanges},
- },
+ return &CombinedCodeActions{
+ Description: description,
+ Changes: edits,
}, nil
}
diff --git a/tsc/internal/ls/codeactions_addmissingattributes.go b/tsc/internal/ls/codeactions_addmissingattributes.go
new file mode 100644
index 0000000000000..153127278424c
--- /dev/null
+++ b/tsc/internal/ls/codeactions_addmissingattributes.go
@@ -0,0 +1,159 @@
+package ls
+
+import (
+ "context"
+
+ "github.com/microsoft/TypeScript/tsc/internal/ast"
+ "github.com/microsoft/TypeScript/tsc/internal/astnav"
+ "github.com/microsoft/TypeScript/tsc/internal/checker"
+ "github.com/microsoft/TypeScript/tsc/internal/collections"
+ "github.com/microsoft/TypeScript/tsc/internal/core"
+ "github.com/microsoft/TypeScript/tsc/internal/diagnostics"
+ "github.com/microsoft/TypeScript/tsc/internal/locale"
+ "github.com/microsoft/TypeScript/tsc/internal/ls/change"
+ "github.com/microsoft/TypeScript/tsc/internal/ls/lsutil"
+ "github.com/microsoft/TypeScript/tsc/internal/scanner"
+)
+
+const fixMissingAttributes = "fixMissingAttributes"
+
+type addMissingJsxAttributesFixer struct {
+ sourceFile *ast.SourceFile
+ typeChecker *checker.Checker
+}
+
+var AddMissingJsxAttributesFixProvider = &CodeFixProvider{
+ ErrorCodes: addMissingPropertiesErrorCodes,
+ GetCodeActions: getCodeActionsToAddMissingJsxAttributes,
+ FixIds: []string{fixMissingAttributes},
+ GetAllCodeActions: getAllCodeActionsToAddMissingJsxAttributes,
+}
+
+func getCodeActionsToAddMissingJsxAttributes(ctx context.Context, fixContext *CodeFixContext) ([]*CodeAction, error) {
+ typeChecker, done := fixContext.Program.GetTypeCheckerForFile(ctx, fixContext.SourceFile)
+ defer done()
+ fix := addMissingJsxAttributesFixer{
+ sourceFile: fixContext.SourceFile,
+ typeChecker: typeChecker,
+ }
+
+ declaration, properties := fix.getInfo(fixContext.Span.Pos())
+ if len(properties) == 0 {
+ return nil, nil
+ }
+
+ tracker := change.NewTracker(ctx, fixContext.Program.Options(), fixContext.LS.FormatOptions(), fixContext.LS.converters)
+ importAdder, err := fixContext.LS.createImportAdder(ctx, typeChecker, fixContext.SourceFile)
+ if err != nil {
+ return nil, err
+ }
+
+ loc := locale.FromContext(ctx)
+ fixer := newMissingMemberFixer(tracker, fixContext.Program, typeChecker, fixContext.LS.UserPreferences(), importAdder, loc)
+ fix.addChanges(fixer, declaration, properties)
+
+ fixer.addImports()
+ if importAdder != nil {
+ importAdder.WriteFixes(tracker)
+ }
+ changes := getCodeFixChanges(fixContext.SourceFile, tracker)
+ if len(changes) == 0 {
+ return nil, nil
+ }
+
+ return []*CodeAction{{
+ Description: diagnostics.Add_missing_attributes.Localize(loc),
+ Changes: changes,
+ FixID: fixMissingAttributes,
+ FixAllDescription: diagnostics.Add_all_missing_attributes.Localize(loc),
+ }}, nil
+}
+
+func getAllCodeActionsToAddMissingJsxAttributes(ctx context.Context, fixContext *CodeFixContext, fixAll *CodeFixAll) (*CombinedCodeActions, error) {
+ fix := addMissingJsxAttributesFixer{
+ sourceFile: fixContext.SourceFile,
+ typeChecker: fixAll.typeChecker,
+ }
+
+ var fixes []missingProperties
+ var seen collections.Set[*ast.Node]
+ for _, diagnostic := range fixAll.diagnostics {
+ if diagnostic.File() == fixContext.SourceFile && isFixableDiagnostic(diagnostic, addMissingPropertiesErrorCodes) {
+ declaration, properties := fix.getInfo(diagnostic.Pos())
+ if len(properties) > 0 && seen.AddIfAbsent(declaration) {
+ fixes = append(fixes, missingProperties{declaration: declaration, properties: properties})
+ }
+ }
+ }
+
+ if len(fixes) == 0 {
+ return nil, nil
+ }
+
+ importAdder, err := fixAll.getImportAdder(ctx, fixContext)
+ if err != nil {
+ return nil, err
+ }
+
+ tracker := change.NewTracker(ctx, fixContext.Program.Options(), fixContext.LS.FormatOptions(), fixContext.LS.converters)
+ loc := locale.FromContext(ctx)
+ fixer := newMissingMemberFixer(tracker, fixContext.Program, fixAll.typeChecker, fixContext.LS.UserPreferences(), importAdder, loc)
+ for _, info := range fixes {
+ fix.addChanges(fixer, info.declaration, info.properties)
+ }
+
+ changes := getCodeFixChanges(fixContext.SourceFile, tracker)
+ if len(changes) == 0 {
+ return nil, nil
+ }
+ fixer.addImports()
+
+ return &CombinedCodeActions{
+ Description: diagnostics.Add_all_missing_attributes.Localize(loc),
+ Changes: changes,
+ }, nil
+}
+
+func (fix *addMissingJsxAttributesFixer) getInfo(pos int) (*ast.Node, []*ast.Symbol) {
+ token := astnav.GetTokenAtPosition(fix.sourceFile, pos)
+ if token == nil || !ast.IsIdentifier(token) || token.Parent == nil || !ast.IsJsxOpeningLikeElement(token.Parent) {
+ return nil, nil
+ }
+
+ declaration := token.Parent
+ attributes := declaration.Attributes()
+ targetType := fix.typeChecker.GetContextualType(attributes, checker.ContextFlagsNone)
+ if targetType == nil {
+ return nil, nil
+ }
+
+ sourceType := fix.typeChecker.GetJsxAttributesType(attributes)
+ return declaration, core.Filter(fix.typeChecker.GetPropertiesOfType(targetType), func(property *ast.Symbol) bool {
+ sourceProperty := fix.typeChecker.GetPropertyOfType(sourceType, property.Name())
+ return scanner.IsIdentifierText(property.Name(), core.LanguageVariantJSX) && property.Flags()&ast.SymbolFlagsOptional == 0 && property.CheckFlags()&ast.CheckFlagsPartial == 0 &&
+ (sourceProperty == nil || sourceProperty.Flags()&ast.SymbolFlagsOptional != 0 || sourceProperty.CheckFlags()&ast.CheckFlagsPartial != 0)
+ })
+}
+
+func (fix *addMissingJsxAttributesFixer) addChanges(f *missingMemberFixer, declaration *ast.Node, properties []*ast.Symbol) {
+ sourceFile := fix.sourceFile
+ factory := f.changeTracker.NodeFactory
+ attributes := declaration.Attributes()
+ quotePreference := lsutil.GetQuotePreference(sourceFile, f.preferences)
+
+ var members []*ast.Node
+ for _, property := range properties {
+ initializer := f.tryGetValueFromType(f.typeChecker.GetTypeOfSymbol(property), declaration, sourceFile, quotePreference, nil /*typeStack*/)
+ name := factory.NewIdentifier(property.Name())
+ attribute := factory.NewJsxAttribute(name, factory.NewJsxExpression(nil /*dotDotDotToken*/, initializer))
+ name.Parent = attribute
+ members = append(members, attribute)
+ }
+
+ pos := attributes.End()
+ if core.Some(attributes.Properties(), ast.IsJsxSpreadAttribute) {
+ pos = attributes.Pos()
+ }
+
+ f.changeTracker.InsertNodeAt(sourceFile, core.TextPos(pos), factory.NewJsxAttributes(factory.NewNodeList(members)), change.NodeOptions{Prefix: " "})
+}
diff --git a/tsc/internal/ls/codeactions_addmissingproperties.go b/tsc/internal/ls/codeactions_addmissingproperties.go
new file mode 100644
index 0000000000000..96eec6373c759
--- /dev/null
+++ b/tsc/internal/ls/codeactions_addmissingproperties.go
@@ -0,0 +1,235 @@
+package ls
+
+import (
+ "context"
+ "slices"
+
+ "github.com/microsoft/TypeScript/tsc/internal/ast"
+ "github.com/microsoft/TypeScript/tsc/internal/astnav"
+ "github.com/microsoft/TypeScript/tsc/internal/checker"
+ "github.com/microsoft/TypeScript/tsc/internal/collections"
+ "github.com/microsoft/TypeScript/tsc/internal/core"
+ "github.com/microsoft/TypeScript/tsc/internal/diagnostics"
+ "github.com/microsoft/TypeScript/tsc/internal/locale"
+ "github.com/microsoft/TypeScript/tsc/internal/ls/change"
+ "github.com/microsoft/TypeScript/tsc/internal/ls/lsutil"
+)
+
+const fixMissingProperties = "fixMissingProperties"
+
+type addMissingPropertiesFixer struct {
+ sourceFile *ast.SourceFile
+ typeChecker *checker.Checker
+}
+
+type missingProperties struct {
+ declaration *ast.Node
+ properties []*ast.Symbol
+}
+
+var addMissingPropertiesErrorCodes = []int32{
+ diagnostics.Property_0_is_missing_in_type_1_but_required_in_type_2.Code(),
+ diagnostics.Type_0_is_missing_the_following_properties_from_type_1_Colon_2.Code(),
+ diagnostics.Type_0_is_missing_the_following_properties_from_type_1_Colon_2_and_3_more.Code(),
+ diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1.Code(),
+ diagnostics.Type_0_is_not_assignable_to_type_1.Code(),
+ diagnostics.Type_0_does_not_satisfy_the_expected_type_1.Code(),
+}
+
+var AddMissingPropertiesFixProvider = &CodeFixProvider{
+ ErrorCodes: addMissingPropertiesErrorCodes,
+ GetCodeActions: getCodeActionsToAddMissingProperties,
+ FixIds: []string{fixMissingProperties},
+ GetAllCodeActions: getAllCodeActionsToAddMissingProperties,
+}
+
+func getCodeActionsToAddMissingProperties(ctx context.Context, fixContext *CodeFixContext) ([]*CodeAction, error) {
+ typeChecker, done := fixContext.Program.GetTypeCheckerForFile(ctx, fixContext.SourceFile)
+ defer done()
+
+ fix := addMissingPropertiesFixer{
+ sourceFile: fixContext.SourceFile,
+ typeChecker: typeChecker,
+ }
+
+ declaration, properties := fix.getInfo(fixContext.Span.Pos(), fixContext.ErrorCode)
+ if len(properties) == 0 {
+ return nil, nil
+ }
+
+ tracker := change.NewTracker(ctx, fixContext.Program.Options(), fixContext.LS.FormatOptions(), fixContext.LS.converters)
+ importAdder, err := fixContext.LS.createImportAdder(ctx, typeChecker, fixContext.SourceFile)
+ if err != nil {
+ return nil, err
+ }
+
+ loc := locale.FromContext(ctx)
+ fixer := newMissingMemberFixer(tracker, fixContext.Program, typeChecker, fixContext.LS.UserPreferences(), importAdder, loc)
+ fix.addChanges(fixer, declaration, properties)
+
+ fixer.addImports()
+ if importAdder != nil {
+ importAdder.WriteFixes(tracker)
+ }
+ changes := getCodeFixChanges(fixContext.SourceFile, tracker)
+ if len(changes) == 0 {
+ return nil, nil
+ }
+
+ return []*CodeAction{{
+ Description: diagnostics.Add_missing_properties.Localize(loc),
+ Changes: changes,
+ FixID: fixMissingProperties,
+ FixAllDescription: diagnostics.Add_all_missing_properties.Localize(loc),
+ }}, nil
+}
+
+func getAllCodeActionsToAddMissingProperties(ctx context.Context, fixContext *CodeFixContext, fixAll *CodeFixAll) (*CombinedCodeActions, error) {
+ fix := addMissingPropertiesFixer{
+ sourceFile: fixContext.SourceFile,
+ typeChecker: fixAll.typeChecker,
+ }
+
+ var fixes []missingProperties
+ var seen collections.Set[*ast.Node]
+ for _, diagnostic := range fixAll.diagnostics {
+ if diagnostic.File() == fixContext.SourceFile && isFixableDiagnostic(diagnostic, addMissingPropertiesErrorCodes) {
+ declaration, properties := fix.getInfo(diagnostic.Pos(), diagnostic.Code())
+ if len(properties) > 0 && seen.AddIfAbsent(declaration) {
+ fixes = append(fixes, missingProperties{declaration: declaration, properties: properties})
+ }
+ }
+ }
+
+ if len(fixes) == 0 {
+ return nil, nil
+ }
+
+ importAdder, err := fixAll.getImportAdder(ctx, fixContext)
+ if err != nil {
+ return nil, err
+ }
+
+ tracker := change.NewTracker(ctx, fixContext.Program.Options(), fixContext.LS.FormatOptions(), fixContext.LS.converters)
+ loc := locale.FromContext(ctx)
+ fixer := newMissingMemberFixer(tracker, fixContext.Program, fixAll.typeChecker, fixContext.LS.UserPreferences(), importAdder, loc)
+ fix.addAllChanges(fixer, fixes)
+
+ changes := getCodeFixChanges(fixContext.SourceFile, tracker)
+ if len(changes) == 0 {
+ return nil, nil
+ }
+ fixer.addImports()
+
+ return &CombinedCodeActions{
+ Description: diagnostics.Add_all_missing_properties.Localize(loc),
+ Changes: changes,
+ }, nil
+}
+
+func (fix *addMissingPropertiesFixer) getInfo(pos int, errorCode int32) (*ast.Node, []*ast.Symbol) {
+ token := astnav.GetTokenAtPosition(fix.sourceFile, pos)
+ if token == nil || token.Parent == nil {
+ return nil, nil
+ }
+
+ parent := token.Parent
+ if errorCode == diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1.Code() || ast.IsObjectLiteralExpression(parent) && ast.IsCallExpression(parent.Parent) {
+ if !(token.Kind == ast.KindOpenBraceToken && ast.IsObjectLiteralExpression(parent) && ast.IsCallExpression(parent.Parent)) {
+ return nil, nil
+ }
+ argumentIndex := slices.Index(parent.Parent.Arguments(), parent)
+ if argumentIndex < 0 {
+ return nil, nil
+ }
+ signature := fix.typeChecker.GetResolvedSignature(parent.Parent)
+ if signature == nil || signature.Declaration() == nil || len(signature.Parameters()) == 0 {
+ return nil, nil
+ }
+ parameterIndex := argumentIndex
+ if signature.HasRestParameter() {
+ parameterIndex = min(parameterIndex, len(signature.Parameters())-1)
+ }
+ if parameterIndex >= len(signature.Parameters()) {
+ return nil, nil
+ }
+ parameter := signature.Parameters()[parameterIndex].ValueDeclaration()
+ if parameter == nil || !ast.IsParameterDeclaration(parameter) || !ast.IsIdentifier(parameter.Name()) {
+ return nil, nil
+ }
+ }
+
+ var literal *ast.Node
+ var targetType *checker.Type
+ if token.Kind == ast.KindOpenBraceToken || ast.IsSatisfiesExpression(parent) || ast.IsReturnStatement(parent) {
+ literal = parent
+ if ast.IsSatisfiesExpression(parent) || ast.IsReturnStatement(parent) {
+ literal = parent.Expression()
+ }
+ if literal == nil || !ast.IsObjectLiteralExpression(literal) {
+ return nil, nil
+ }
+ if ast.IsSatisfiesExpression(parent) {
+ targetType = fix.typeChecker.GetTypeFromTypeNode(parent.Type())
+ } else {
+ targetType = fix.typeChecker.GetContextualType(literal, checker.ContextFlagsNone)
+ }
+ } else if ast.IsIdentifier(token) && ast.HasInitializer(parent) && parent.Initializer() != nil && ast.IsObjectLiteralExpression(parent.Initializer()) {
+ literal = parent.Initializer()
+ targetType = core.OrElse(fix.typeChecker.GetContextualType(token, checker.ContextFlagsNone), fix.typeChecker.GetTypeAtLocation(token))
+ } else {
+ return nil, nil
+ }
+
+ if targetType == nil {
+ return nil, nil
+ }
+
+ return literal, fix.typeChecker.GetUnmatchedProperties(fix.typeChecker.GetTypeAtLocation(literal), fix.typeChecker.GetNonNullableType(targetType), false /*requireOptionalProperties*/, false /*matchDiscriminantProperties*/)
+}
+
+func (fix *addMissingPropertiesFixer) addChanges(f *missingMemberFixer, literal *ast.Node, properties []*ast.Symbol) {
+ factory := f.changeTracker.NodeFactory
+ quotePreference := lsutil.GetQuotePreference(fix.sourceFile, f.preferences)
+ members := append([]*ast.Node{}, literal.AsObjectLiteralExpression().Properties.Nodes...)
+ members = append(members, fix.createMissingProperties(f, literal, properties, quotePreference)...)
+ fix.replaceObjectLiteral(f, literal, factory.NewObjectLiteralExpression(factory.NewNodeList(members), true /*multiLine*/))
+}
+
+func (fix *addMissingPropertiesFixer) addAllChanges(f *missingMemberFixer, fixes []missingProperties) {
+ factory := f.changeTracker.NodeFactory
+ quotePreference := lsutil.GetQuotePreference(fix.sourceFile, f.preferences)
+ for _, info := range fixes {
+ members := fix.createMissingProperties(f, info.declaration, info.properties, quotePreference)
+ properties := info.declaration.AsObjectLiteralExpression().Properties
+ if len(properties.Nodes) == 0 {
+ fix.replaceObjectLiteral(f, info.declaration, factory.NewObjectLiteralExpression(factory.NewNodeList(members), true /*multiLine*/))
+ } else {
+ f.changeTracker.InsertNodesInListAfter(fix.sourceFile, properties.Nodes[len(properties.Nodes)-1], members, properties)
+ }
+ }
+}
+
+func (fix *addMissingPropertiesFixer) createMissingProperties(f *missingMemberFixer, literal *ast.Node, properties []*ast.Symbol, quotePreference lsutil.QuotePreference) []*ast.Node {
+ factory := f.changeTracker.NodeFactory
+ members := make([]*ast.Node, 0, len(properties))
+ for _, property := range properties {
+ initializer := f.tryGetValueFromType(f.typeChecker.GetTypeOfSymbol(property), literal, fix.sourceFile, quotePreference, nil /*typeStack*/)
+ members = append(members, factory.NewPropertyAssignment(nil /*modifiers*/, f.createPropertyNameFromSymbol(property, literal, quotePreference), nil /*postfixToken*/, nil /*typeNode*/, initializer))
+ }
+ return members
+}
+
+func (fix *addMissingPropertiesFixer) replaceObjectLiteral(f *missingMemberFixer, literal *ast.Node, replacement *ast.Node) {
+ var options *change.NodeOptions
+ if ast.IsReturnStatement(literal.Parent) || ast.IsYieldExpression(literal.Parent) {
+ indentation := 0
+ options = &change.NodeOptions{
+ LeadingTriviaOption: change.LeadingTriviaOptionExclude,
+ TrailingTriviaOption: change.TrailingTriviaOptionExclude,
+ Indentation: &indentation,
+ }
+ }
+
+ f.changeTracker.ReplaceNode(fix.sourceFile, literal, replacement, options)
+}
diff --git a/tsc/internal/ls/codeactions_fixclassincorrectlyimplementsinterface.go b/tsc/internal/ls/codeactions_fixclassincorrectlyimplementsinterface.go
index b91cc1b1c44b4..7ed5073eda995 100644
--- a/tsc/internal/ls/codeactions_fixclassincorrectlyimplementsinterface.go
+++ b/tsc/internal/ls/codeactions_fixclassincorrectlyimplementsinterface.go
@@ -12,7 +12,6 @@ import (
"github.com/microsoft/TypeScript/tsc/internal/locale"
"github.com/microsoft/TypeScript/tsc/internal/ls/autoimport"
"github.com/microsoft/TypeScript/tsc/internal/ls/change"
- "github.com/microsoft/TypeScript/tsc/internal/lsp/lsproto"
"github.com/microsoft/TypeScript/tsc/internal/scanner"
)
@@ -45,13 +44,17 @@ func getCodeActionsToFixClassIncorrectlyImplementsInterface(context context.Cont
var actions []*CodeAction
for _, implementedTypeNode := range implementsTypes {
changeTracker := change.NewTracker(context, fixContext.Program.Options(), fixContext.LS.FormatOptions(), fixContext.LS.converters)
- importAdder, err := createImportAdder(context, fixContext, typeChecker)
+ importAdder, err := fixContext.LS.createImportAdder(context, typeChecker, fixContext.SourceFile)
if err != nil {
return nil, err
}
- addChanges(context, fixContext, changeTracker, importAdder, typeChecker, classDeclaration, implementedTypeNode)
- changes := getChanges(changeTracker, importAdder, fixContext.SourceFile)
+ fixer := addChanges(context, fixContext, changeTracker, importAdder, typeChecker, classDeclaration, implementedTypeNode)
+ fixer.addImports()
+ if importAdder != nil {
+ importAdder.WriteFixes(changeTracker)
+ }
+ changes := getCodeFixChanges(fixContext.SourceFile, changeTracker)
if len(changes) == 0 {
continue
}
@@ -66,39 +69,37 @@ func getCodeActionsToFixClassIncorrectlyImplementsInterface(context context.Cont
return actions, nil
}
-func getAllCodeActionsToFixClassIncorrectlyImplementsInterface(context context.Context, fixContext *CodeFixContext) (*CombinedCodeActions, error) {
- allDiags := getAllDiagnostics(context, fixContext.Program, fixContext.SourceFile)
-
- typeChecker, done := fixContext.Program.GetTypeCheckerForFile(context, fixContext.SourceFile)
- defer done()
-
+func getAllCodeActionsToFixClassIncorrectlyImplementsInterface(context context.Context, fixContext *CodeFixContext, fixAll *CodeFixAll) (*CombinedCodeActions, error) {
changeTracker := change.NewTracker(context, fixContext.Program.Options(), fixContext.LS.FormatOptions(), fixContext.LS.converters)
- importAdder, err := createImportAdder(context, fixContext, typeChecker)
- if err != nil {
- return nil, err
- }
-
seenClassDeclarations := collections.Set[*ast.Node]{}
+ var fixers []*missingMemberFixer
- for _, diag := range allDiags {
- if isFixableDiagnostic(diag, fixClassIncorrectlyImplementsInterfaceErrorCodes) {
+ for _, diag := range fixAll.diagnostics {
+ if diag.File() == fixContext.SourceFile && isFixableDiagnostic(diag, fixClassIncorrectlyImplementsInterfaceErrorCodes) {
classDeclaration := getClass(fixContext.SourceFile, core.NewTextRange(diag.Pos(), diag.End()))
if classDeclaration == nil {
continue
}
if seenClassDeclarations.AddIfAbsent(classDeclaration) {
+ importAdder, err := fixAll.getImportAdder(context, fixContext)
+ if err != nil {
+ return nil, err
+ }
implementsTypes := ast.GetImplementsHeritageClauseElements(classDeclaration)
for _, implementedTypeNode := range implementsTypes {
- addChanges(context, fixContext, changeTracker, importAdder, typeChecker, classDeclaration, implementedTypeNode)
+ fixers = append(fixers, addChanges(context, fixContext, changeTracker, importAdder, fixAll.typeChecker, classDeclaration, implementedTypeNode))
}
}
}
}
- changes := getChanges(changeTracker, importAdder, fixContext.SourceFile)
+ changes := getCodeFixChanges(fixContext.SourceFile, changeTracker)
if len(changes) == 0 {
return nil, nil
}
+ for _, fixer := range fixers {
+ fixer.addImports()
+ }
return &CombinedCodeActions{
Description: diagnostics.Implement_all_unimplemented_interfaces.Localize(locale.FromContext(context)),
@@ -106,7 +107,7 @@ func getAllCodeActionsToFixClassIncorrectlyImplementsInterface(context context.C
}, nil
}
-func addChanges(context context.Context, fixContext *CodeFixContext, changeTracker *change.Tracker, importAdder autoimport.ImportAdder, typeChecker *checker.Checker, classDeclaration *ast.Node, implementedTypeNode *ast.HeritageClauseElement) {
+func addChanges(context context.Context, fixContext *CodeFixContext, changeTracker *change.Tracker, importAdder autoimport.ImportAdder, typeChecker *checker.Checker, classDeclaration *ast.Node, implementedTypeNode *ast.HeritageClauseElement) *missingMemberFixer {
missingMemberFixer := newMissingMemberFixer(changeTracker, fixContext.Program, typeChecker, fixContext.LS.UserPreferences(), importAdder, locale.FromContext(context))
constructor := getConstructor(classDeclaration)
implementedType := typeChecker.GetTypeAtLocation(implementedTypeNode)
@@ -133,18 +134,7 @@ func addChanges(context context.Context, fixContext *CodeFixContext, changeTrack
insertInterfaceMemberNode(changeTracker, fixContext.SourceFile, classDeclaration, constructor, memberNode)
}
}
-}
-
-func getChanges(changeTracker *change.Tracker, importAdder autoimport.ImportAdder, sourceFile *ast.SourceFile) []*lsproto.TextEdit {
- changes, unmappable := changeTracker.GetChanges()
- if len(unmappable) != 0 {
- return nil
- }
- fileChanges := changes[sourceFile.OriginalFileName()]
- if importAdder != nil && importAdder.HasFixes() {
- fileChanges = append(fileChanges, importAdder.Edits()...)
- }
- return fileChanges
+ return missingMemberFixer
}
func insertInterfaceMemberNode(changeTracker *change.Tracker, sourceFile *ast.SourceFile, classDeclaration *ast.Node, constructor *ast.Node, member *ast.Node) {
@@ -229,14 +219,3 @@ func getInheritedMembers(typeChecker *checker.Checker, classDeclaration *ast.Nod
}
return inheritedMembers
}
-
-func createImportAdder(context context.Context, fixContext *CodeFixContext, typeChecker *checker.Checker) (autoimport.ImportAdder, error) {
- view, err := fixContext.LS.getPreparedAutoImportView(fixContext.SourceFile, typeChecker)
- if err != nil {
- return nil, err
- }
- if view == nil {
- return nil, nil
- }
- return autoimport.NewImportAdder(context, fixContext.Program, typeChecker, fixContext.SourceFile, view, fixContext.LS.FormatOptions(), fixContext.LS.converters, fixContext.LS.UserPreferences()), nil
-}
diff --git a/tsc/internal/ls/codeactions_fixmissingtypeannotation.go b/tsc/internal/ls/codeactions_fixmissingtypeannotation.go
index 783f2207f78aa..ec0a3d4e0362b 100644
--- a/tsc/internal/ls/codeactions_fixmissingtypeannotation.go
+++ b/tsc/internal/ls/codeactions_fixmissingtypeannotation.go
@@ -125,44 +125,45 @@ func getIsolatedDeclarationsCodeActions(ctx context.Context, fixContext *CodeFix
return fixes, nil
}
-func getAllIsolatedDeclarationsCodeActions(ctx context.Context, fixContext *CodeFixContext) (*CombinedCodeActions, error) {
- allDiags := getAllDiagnostics(ctx, fixContext.Program, fixContext.SourceFile)
-
- ch, done := fixContext.Program.GetTypeCheckerForFile(ctx, fixContext.SourceFile)
- defer done()
-
+func getAllIsolatedDeclarationsCodeActions(ctx context.Context, fixContext *CodeFixContext, fixAll *CodeFixAll) (*CombinedCodeActions, error) {
changeTracker := change.NewTracker(ctx, fixContext.Program.Options(), fixContext.LS.FormatOptions(), fixContext.LS.converters)
-
fixer := &isolatedDeclarationsFixer{
sourceFile: fixContext.SourceFile,
program: fixContext.Program,
- checker: ch,
+ checker: fixAll.typeChecker,
changeTracker: changeTracker,
locale: locale.FromContext(ctx),
fixedNodes: make(map[*ast.Node]bool),
typePrintMode: typePrintModeFull,
}
- for _, diag := range allDiags {
- if isFixableDiagnostic(diag, isolatedDeclarationsFixErrorCodes) {
+ for _, diag := range fixAll.diagnostics {
+ if diag.File() == fixContext.SourceFile && isFixableDiagnostic(diag, isolatedDeclarationsFixErrorCodes) {
span := core.NewTextRange(diag.Loc().Pos(), diag.Loc().End())
fixer.addTypeAnnotation(span)
}
}
- for _, sym := range fixer.symbolsToImport {
- fixer.addSymbolToExistingImport(sym)
+ changes := getCodeFixChanges(fixContext.SourceFile, changeTracker)
+ if len(changes) == 0 {
+ return nil, nil
}
- changes, _ := changeTracker.GetChanges()
- fileChanges := changes[fixContext.SourceFile.OriginalFileName()]
- if len(fileChanges) == 0 {
- return nil, nil
+ if len(fixer.symbolsToImport) > 0 {
+ importAdder, err := fixAll.getImportAdder(ctx, fixContext)
+ if err != nil {
+ return nil, err
+ }
+ if importAdder != nil {
+ for _, symbol := range fixer.symbolsToImport {
+ importAdder.AddImportFromExportedSymbol(symbol, true /*isValidTypeOnlyUseSite*/)
+ }
+ }
}
return &CombinedCodeActions{
Description: diagnostics.Add_all_missing_type_annotations.Localize(locale.FromContext(ctx)),
- Changes: fileChanges,
+ Changes: changes,
}, nil
}
@@ -193,14 +194,11 @@ func tryCodeAction(ctx context.Context, fixContext *CodeFixContext, ch *checker.
fixer.addSymbolToExistingImport(sym)
}
- changes, _ := changeTracker.GetChanges()
- fileChanges := changes[fixContext.SourceFile.OriginalFileName()]
-
// Add import edits if import adder has fixes
if importAdder != nil && importAdder.HasFixes() {
- fileChanges = append(fileChanges, importAdder.Edits()...)
+ importAdder.WriteFixes(changeTracker)
}
-
+ fileChanges := getCodeFixChanges(fixContext.SourceFile, changeTracker)
if len(fileChanges) == 0 {
return nil
}
diff --git a/tsc/internal/ls/codeactions_importfixes.go b/tsc/internal/ls/codeactions_importfixes.go
index 0055dfc74e4c2..e3691bdb257c5 100644
--- a/tsc/internal/ls/codeactions_importfixes.go
+++ b/tsc/internal/ls/codeactions_importfixes.go
@@ -94,16 +94,14 @@ func getImportCodeActions(ctx context.Context, fixContext *CodeFixContext) ([]*C
return actions, nil
}
-func getAllImportCodeActions(ctx context.Context, fixContext *CodeFixContext) (*CombinedCodeActions, error) {
+func getAllImportCodeActions(ctx context.Context, fixContext *CodeFixContext, fixAll *CodeFixAll) (*CombinedCodeActions, error) {
if fixContext.SourceFile.FileName().IsDynamic() {
return nil, nil
}
- allDiagnostics := fixContext.Program.GetSemanticDiagnostics(ctx, fixContext.SourceFile)
-
var importDiags []*ast.Diagnostic
- for _, diag := range allDiagnostics {
- if isFixableDiagnostic(diag, importFixErrorCodes) {
+ for _, diag := range fixAll.diagnostics {
+ if diag.File() == fixContext.SourceFile && isFixableDiagnostic(diag, importFixErrorCodes) {
importDiags = append(importDiags, diag)
}
}
@@ -112,41 +110,20 @@ func getAllImportCodeActions(ctx context.Context, fixContext *CodeFixContext) (*
return nil, nil
}
- ch, done := fixContext.Program.GetTypeChecker(ctx)
- defer done()
-
- view, err := fixContext.LS.getPreparedAutoImportView(fixContext.SourceFile, ch)
+ importAdder, err := fixAll.getImportAdder(ctx, fixContext)
if err != nil {
return nil, err
}
- if view == nil {
- view = fixContext.LS.getCurrentAutoImportView(fixContext.SourceFile, ch)
- }
-
- importAdder := autoimport.NewImportAdder(
- ctx,
- fixContext.Program,
- ch,
- fixContext.SourceFile,
- view,
- fixContext.LS.FormatOptions(),
- fixContext.LS.converters,
- fixContext.LS.UserPreferences(),
- )
for _, diag := range importDiags {
- if err := addImportFromDiagnostic(ch, importAdder, diag, fixContext); err != nil {
+ err := addImportFromDiagnostic(fixAll.typeChecker, importAdder, diag, fixContext)
+ if err != nil {
return nil, err
}
}
- if !importAdder.HasFixes() {
- return nil, nil
- }
-
return &CombinedCodeActions{
Description: diagnostics.Add_all_missing_imports.Localize(locale.FromContext(ctx)),
- Changes: importAdder.Edits(),
}, nil
}
diff --git a/tsc/internal/ls/codeactions_missingmemberfixer.go b/tsc/internal/ls/codeactions_missingmemberfixer.go
index 4f1cf6caf0a36..3efb39b1be6dc 100644
--- a/tsc/internal/ls/codeactions_missingmemberfixer.go
+++ b/tsc/internal/ls/codeactions_missingmemberfixer.go
@@ -1,18 +1,24 @@
package ls
import (
+ "slices"
"strconv"
+ "strings"
"github.com/microsoft/TypeScript/tsc/internal/ast"
"github.com/microsoft/TypeScript/tsc/internal/checker"
+ "github.com/microsoft/TypeScript/tsc/internal/collections"
"github.com/microsoft/TypeScript/tsc/internal/compiler"
"github.com/microsoft/TypeScript/tsc/internal/core"
"github.com/microsoft/TypeScript/tsc/internal/diagnostics"
+ "github.com/microsoft/TypeScript/tsc/internal/jsnum"
"github.com/microsoft/TypeScript/tsc/internal/locale"
"github.com/microsoft/TypeScript/tsc/internal/ls/autoimport"
"github.com/microsoft/TypeScript/tsc/internal/ls/change"
"github.com/microsoft/TypeScript/tsc/internal/ls/lsutil"
+ "github.com/microsoft/TypeScript/tsc/internal/lsp/lsproto"
"github.com/microsoft/TypeScript/tsc/internal/nodebuilder"
+ "github.com/microsoft/TypeScript/tsc/internal/scanner"
)
type preserveOptionalFlags int
@@ -24,12 +30,14 @@ const (
)
type missingMemberFixer struct {
- changeTracker *change.Tracker
- typeChecker *checker.Checker
- program *compiler.Program
- preferences lsutil.UserPreferences
- importAdder autoimport.ImportAdder
- locale locale.Locale
+ changeTracker *change.Tracker
+ typeChecker *checker.Checker
+ program *compiler.Program
+ preferences lsutil.UserPreferences
+ importAdder autoimport.ImportAdder
+ locale locale.Locale
+ symbolsToImport map[*ast.Symbol]bool
+ importsToPromote collections.Set[*ast.Node]
}
func newMissingMemberFixer(changeTracker *change.Tracker, program *compiler.Program, typeChecker *checker.Checker, preferences lsutil.UserPreferences, importAdder autoimport.ImportAdder, locale locale.Locale) *missingMemberFixer {
@@ -260,7 +268,7 @@ func (f *missingMemberFixer) createSignatureDeclarationFromSignature(signature *
}
parameter := p.AsParameterDeclaration()
- parameterTypeNode := parameter.Type
+ parameterTypeNode := core.IfElse(isJS, nil /*parameterTypeNode*/, parameter.Type)
if parameterTypeNode != nil {
parameterTypeNode = f.importTypeNode(parameterTypeNode, idToSymbol)
}
@@ -382,7 +390,7 @@ func (f *missingMemberFixer) importTypeNode(typeNode *ast.TypeNode, idToSymbol m
if exportSymbol == nil {
continue
}
- f.importAdder.AddImportFromExportedSymbol(exportSymbol, true /*isValidTypeOnlyUseSite*/)
+ f.addSymbolToImport(exportSymbol, true /*isValidTypeOnlyUseSite*/)
}
return importedTypeNode
}
@@ -397,11 +405,34 @@ func (f *missingMemberFixer) importTypeNode(typeNode *ast.TypeNode, idToSymbol m
if exportSymbol == nil {
continue
}
- f.importAdder.AddImportFromExportedSymbol(exportSymbol, true /*isValidTypeOnlyUseSite*/)
+ f.addSymbolToImport(exportSymbol, true /*isValidTypeOnlyUseSite*/)
}
return typeNode
}
+func (f *missingMemberFixer) addSymbolToImport(symbol *ast.Symbol, isValidTypeOnlyUseSite bool) {
+ if f.symbolsToImport == nil {
+ f.symbolsToImport = make(map[*ast.Symbol]bool)
+ }
+ previous, exists := f.symbolsToImport[symbol]
+ f.symbolsToImport[symbol] = isValidTypeOnlyUseSite && (!exists || previous)
+}
+
+func (f *missingMemberFixer) addImports() {
+ if f.importAdder == nil {
+ return
+ }
+ for symbol, isValidTypeOnlyUseSite := range f.symbolsToImport {
+ f.importAdder.AddImportFromExportedSymbol(symbol, isValidTypeOnlyUseSite)
+ }
+ for declaration := range f.importsToPromote.Keys() {
+ f.importAdder.AddImportFix(&autoimport.Fix{
+ AutoImportFix: &lsproto.AutoImportFix{Kind: lsproto.AutoImportFixKindPromoteTypeOnly},
+ TypeOnlyAliasDeclaration: declaration,
+ })
+ }
+}
+
func (f *missingMemberFixer) getExportedSymbol(symbol *ast.Symbol) *ast.Symbol {
symbol = f.typeChecker.GetExportSymbolOfSymbol(symbol)
if symbol == nil || symbol.Parent() == nil {
@@ -432,6 +463,10 @@ func (f *missingMemberFixer) createBody(body *ast.FunctionBody, quotePreference
}
func (f *missingMemberFixer) createStubbedMethodBody(quotePreference lsutil.QuotePreference) *ast.FunctionBody {
+ return f.createStubbedBody(quotePreference, diagnostics.Method_not_implemented.Localize(f.locale))
+}
+
+func (f *missingMemberFixer) createStubbedBody(quotePreference lsutil.QuotePreference, message string) *ast.FunctionBody {
tokenFlags := ast.TokenFlagsNone
if quotePreference == lsutil.QuotePreferenceSingle {
tokenFlags = ast.TokenFlagsSingleQuote
@@ -441,13 +476,179 @@ func (f *missingMemberFixer) createStubbedMethodBody(quotePreference lsutil.Quot
f.changeTracker.NodeFactory.NewThrowStatement(
f.changeTracker.NodeFactory.NewNewExpression(
f.changeTracker.NodeFactory.NewIdentifier("Error"), nil /*typeArguments*/, f.changeTracker.NodeFactory.NewNodeList([]*ast.Node{
- f.changeTracker.NodeFactory.NewStringLiteral(diagnostics.Method_not_implemented.Localize(f.locale), tokenFlags),
+ f.changeTracker.NodeFactory.NewStringLiteral(message, tokenFlags),
}),
),
),
}), true /*multiLine*/)
}
+func (f *missingMemberFixer) createPropertyNameFromSymbol(symbol *ast.Symbol, enclosingDeclaration *ast.Node, quotePreference lsutil.QuotePreference) *ast.Node {
+ factory := f.changeTracker.NodeFactory
+ if symbol.Flags()&ast.SymbolFlagsTransient != 0 {
+ nameType := f.typeChecker.GetNameTypeOfSymbol(symbol)
+ if nameType != nil && nameType.Flags()&(checker.TypeFlagsEnumLiteral|checker.TypeFlagsUniqueESSymbol) != 0 {
+ expression := f.createExpressionFromSymbol(nameType.Symbol(), enclosingDeclaration)
+ if expression != nil {
+ return factory.NewComputedPropertyName(expression)
+ }
+ }
+ builder := checker.NewNodeBuilder(f.typeChecker, f.changeTracker.EmitContext)
+ name := builder.SymbolToNode(symbol, ast.SymbolFlagsValue, enclosingDeclaration, nodebuilder.FlagsNone, nodebuilder.InternalFlagsWriteComputedProps, nil /*tracker*/)
+ if name != nil && ast.IsComputedPropertyName(name) {
+ nameSymbol := f.typeChecker.GetSymbolAtLocation(name.Expression())
+ if nameSymbol != nil {
+ expression := f.createExpressionFromSymbol(nameSymbol, enclosingDeclaration)
+ if expression != nil {
+ return factory.NewComputedPropertyName(expression)
+ }
+ }
+ return factory.DeepCloneNode(name)
+ }
+ }
+ if scanner.IsIdentifierText(symbol.Name(), core.LanguageVariantStandard) {
+ return factory.NewIdentifier(symbol.Name())
+ }
+ value := jsnum.FromString(symbol.Name())
+ if value >= 0 && value.String() == symbol.Name() {
+ return factory.NewNumericLiteral(symbol.Name(), ast.TokenFlagsNone)
+ }
+ return factory.NewStringLiteral(symbol.Name(), core.IfElse(quotePreference == lsutil.QuotePreferenceSingle, ast.TokenFlagsSingleQuote, ast.TokenFlagsNone))
+}
+
+func (f *missingMemberFixer) createExpressionFromSymbol(symbol *ast.Symbol, enclosingDeclaration *ast.Node) *ast.Node {
+ builder, idToSymbol := f.createNodeBuilder()
+ expression := builder.SymbolToExpression(symbol, ast.SymbolFlagsValue, enclosingDeclaration, nodebuilder.FlagsUseFullyQualifiedType, nodebuilder.InternalFlagsNone, nil /*tracker*/)
+ if expression == nil || f.importAdder == nil {
+ return expression
+ }
+
+ identifier := ast.GetLeftmostExpression(expression, true /*stopAtCallExpressions*/)
+ rootSymbol := idToSymbol[identifier]
+ if rootSymbol == nil {
+ return expression
+ }
+ declaration := f.typeChecker.GetTypeOnlyAliasDeclaration(rootSymbol)
+ if declaration != nil && ast.GetSourceFileOfNode(declaration) == ast.GetSourceFileOfNode(enclosingDeclaration) {
+ f.importsToPromote.Add(declaration)
+ } else {
+ resolvedSymbol := f.typeChecker.ResolveName(identifier.Text(), enclosingDeclaration, ast.SymbolFlagsValue, false /*excludeGlobals*/)
+ if resolvedSymbol != nil && f.typeChecker.GetMergedSymbol(f.typeChecker.SkipAlias(resolvedSymbol)) == f.typeChecker.GetMergedSymbol(f.typeChecker.SkipAlias(rootSymbol)) {
+ return expression
+ }
+ exportSymbol := f.getExportedSymbol(rootSymbol)
+ if exportSymbol != nil {
+ f.addSymbolToImport(exportSymbol, false /*isValidTypeOnlyUseSite*/)
+ }
+ }
+ return expression
+}
+
+func (f *missingMemberFixer) tryGetValueFromType(t *checker.Type, enclosingDeclaration *ast.Node, sourceFile *ast.SourceFile, quotePreference lsutil.QuotePreference, typeStack []*checker.Type) *ast.Node {
+ factory := f.changeTracker.NodeFactory
+ if slices.Contains(typeStack, t) || f.typeChecker.IsDeeplyNestedType(t, typeStack, 3 /*maxDepth*/) {
+ return factory.NewIdentifier("undefined")
+ }
+ typeStack = append(typeStack, t)
+ tokenFlags := core.IfElse(quotePreference == lsutil.QuotePreferenceSingle, ast.TokenFlagsSingleQuote, ast.TokenFlagsNone)
+ flags := t.Flags()
+ switch {
+ case flags&checker.TypeFlagsAnyOrUnknown != 0:
+ return factory.NewIdentifier("undefined")
+ case flags&(checker.TypeFlagsString|checker.TypeFlagsTemplateLiteral) != 0:
+ return factory.NewStringLiteral("", tokenFlags)
+ case flags&checker.TypeFlagsNumber != 0:
+ return factory.NewNumericLiteral("0", ast.TokenFlagsNone)
+ case flags&checker.TypeFlagsBigInt != 0:
+ return factory.NewBigIntLiteral("0n", ast.TokenFlagsNone)
+ case flags&checker.TypeFlagsBoolean != 0:
+ return factory.NewKeywordExpression(ast.KindFalseKeyword)
+ case flags&checker.TypeFlagsEnumLike != 0:
+ member := t.Symbol()
+ if member.Flags()&ast.SymbolFlagsEnum != 0 {
+ member = nil
+ for _, declaration := range t.Symbol().Declarations() {
+ if ast.IsEnumDeclaration(declaration) && len(declaration.AsEnumDeclaration().Members.Nodes) > 0 {
+ member = f.typeChecker.GetSymbolOfDeclaration(declaration.AsEnumDeclaration().Members.Nodes[0])
+ break
+ }
+ }
+ }
+ if member != nil {
+ expression := f.createExpressionFromSymbol(member, enclosingDeclaration)
+ if expression != nil {
+ return expression
+ }
+ }
+ return factory.NewNumericLiteral("0", ast.TokenFlagsNone)
+ case flags&checker.TypeFlagsStringLiteral != 0:
+ return factory.NewStringLiteral(t.AsLiteralType().Value().(string), tokenFlags)
+ case flags&(checker.TypeFlagsNumberLiteral|checker.TypeFlagsBigIntLiteral) != 0:
+ text := t.AsLiteralType().String()
+ negative := strings.HasPrefix(text, "-")
+ text = strings.TrimPrefix(text, "-")
+ var literal *ast.Node
+ if flags&checker.TypeFlagsBigIntLiteral != 0 {
+ literal = factory.NewBigIntLiteral(text, ast.TokenFlagsNone)
+ } else {
+ literal = factory.NewNumericLiteral(text, ast.TokenFlagsNone)
+ }
+ if negative {
+ return factory.NewPrefixUnaryExpression(ast.KindMinusToken, literal)
+ }
+ return literal
+ case flags&checker.TypeFlagsBooleanLiteral != 0:
+ return factory.NewKeywordExpression(core.IfElse(t.AsLiteralType().Value().(bool), ast.KindTrueKeyword, ast.KindFalseKeyword))
+ case flags&checker.TypeFlagsNull != 0:
+ return factory.NewKeywordExpression(ast.KindNullKeyword)
+ case t.IsUnion():
+ return f.tryGetValueFromType(t.Types()[0], enclosingDeclaration, sourceFile, quotePreference, typeStack)
+ case f.typeChecker.IsArrayLikeType(t):
+ return factory.NewArrayLiteralExpression(nil /*elements*/, false /*multiLine*/)
+ }
+
+ symbol := t.Symbol()
+ if flags&checker.TypeFlagsObject != 0 && (t.ObjectFlags()&checker.ObjectFlagsObjectLiteral != 0 || symbol != nil && len(symbol.Declarations()) == 1 && ast.IsTypeLiteralNode(symbol.Declarations()[0])) {
+ var properties []*ast.Node
+ for _, property := range f.typeChecker.GetPropertiesOfType(t) {
+ initializer := f.tryGetValueFromType(f.typeChecker.GetTypeOfSymbol(property), enclosingDeclaration, sourceFile, quotePreference, typeStack)
+ properties = append(properties, factory.NewPropertyAssignment(nil /*modifiers*/, f.createPropertyNameFromSymbol(property, enclosingDeclaration, quotePreference), nil /*postfixToken*/, nil /*typeNode*/, initializer))
+ }
+ return factory.NewObjectLiteralExpression(factory.NewNodeList(properties), true /*multiLine*/)
+ }
+ if t.ObjectFlags()&checker.ObjectFlagsAnonymous != 0 && symbol != nil {
+ for _, declaration := range symbol.Declarations() {
+ if ast.IsFunctionTypeNode(declaration) || declaration.Kind == ast.KindMethodSignature || ast.IsMethodDeclaration(declaration) {
+ signature := core.FirstOrNil(f.typeChecker.GetCallSignatures(t))
+ if signature != nil {
+ body := f.createStubbedBody(quotePreference, diagnostics.Function_not_implemented.Localize(f.locale))
+ function := f.createSignatureDeclarationFromSignature(signature, ast.KindFunctionExpression, sourceFile, enclosingDeclaration, body, nil /*modifiers*/, nil /*name*/, false /*optional*/)
+ if function != nil {
+ return function
+ }
+ }
+ break
+ }
+ }
+ }
+ if t.IsClass() {
+ declaration := ast.GetClassLikeDeclarationOfSymbol(symbol)
+ if declaration != nil && !ast.HasAbstractModifier(declaration) {
+ constructorType := f.typeChecker.GetTypeOfSymbol(symbol)
+ signatures := f.typeChecker.GetSignaturesOfType(constructorType, checker.SignatureKindConstruct)
+ if f.typeChecker.IsConstructorAccessible(enclosingDeclaration, signatures) && core.Some(signatures, func(signature *checker.Signature) bool {
+ return f.typeChecker.GetMinArgumentCount(signature) == 0
+ }) {
+ expression := f.createExpressionFromSymbol(symbol, enclosingDeclaration)
+ if expression != nil {
+ return factory.NewNewExpression(expression, nil /*typeArguments*/, nil /*arguments*/)
+ }
+ }
+ }
+ }
+ return factory.NewIdentifier("undefined")
+}
+
func createDummyParameters(factory *ast.NodeFactory, argCount int, names []string, types []*ast.TypeNode, minArgumentCount int, inJS bool) *ast.ParameterList {
parameters := make([]*ast.Node, 0, argCount)
parameterNameCounts := make(map[string]int)
diff --git a/tsc/internal/ls/completions.go b/tsc/internal/ls/completions.go
index 2bfa4fb9faba9..1156e3cd3f2b7 100644
--- a/tsc/internal/ls/completions.go
+++ b/tsc/internal/ls/completions.go
@@ -2662,9 +2662,16 @@ func (l *LanguageService) getEntryForMemberCompletion(ctx context.Context, typeC
}
nodes := fixer.createMemberFromSymbol(symbol, classLikeDeclaration, file, body, preserveOptionalFlagsProperty, abstract)
+ fixer.addImports()
var additionalTextEdits []*lsproto.TextEdit
if importAdder != nil && importAdder.HasFixes() {
- additionalTextEdits = importAdder.Edits()
+ importTracker := change.NewTracker(ctx, l.GetProgram().Options(), l.FormatOptions(), l.converters)
+ importAdder.WriteFixes(importTracker)
+ changes, unmappable := importTracker.GetChanges()
+ if len(unmappable) != 0 {
+ return nil, nil
+ }
+ additionalTextEdits = changes[file.OriginalFileName()]
}
if presentModifiers.eraseRange != nil {
additionalTextEdits = append(additionalTextEdits, &lsproto.TextEdit{
@@ -6697,7 +6704,14 @@ func (l *LanguageService) getExhaustiveCaseSnippets(
var additionalTextEdits *[]*lsproto.TextEdit
if importAdder != nil {
- if edits := importAdder.Edits(); len(edits) != 0 {
+ importTracker := change.NewTracker(ctx, options, l.FormatOptions(), l.converters)
+ importAdder.WriteFixes(importTracker)
+ changes, unmappable := importTracker.GetChanges()
+ if len(unmappable) != 0 {
+ return nil, nil
+ }
+ edits := changes[file.OriginalFileName()]
+ if len(edits) != 0 {
additionalTextEdits = &edits
}
}
diff --git a/tsc/internal/testutil/contentmappertest/editing.go b/tsc/internal/testutil/contentmappertest/editing.go
index d295837a46bbe..152966b152353 100644
--- a/tsc/internal/testutil/contentmappertest/editing.go
+++ b/tsc/internal/testutil/contentmappertest/editing.go
@@ -23,7 +23,10 @@ func (prefixedSupplementalHandler) HandleRequest(ctx context.Context, method str
if err := json.Unmarshal(params, &p); err != nil {
return nil, err
}
- const prefix = "/* generated */\n"
+ prefix := "/* generated */\n"
+ if strings.Contains(p.FileName, "codefix-unmapped-import") {
+ prefix = "import type { C } from \"./a\";\n"
+ }
features := spanmap.FeatureAll
if strings.Contains(p.FileName, "folding-disabled") || strings.Contains(p.FileName, "codelens-disabled") || strings.Contains(p.FileName, "formatting-disabled") {
features = spanmap.FeatureNone