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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 48 additions & 9 deletions internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ const (
CheckModeForceTuple CheckMode = 1 << 7
)

type deprecatedSuggestionKey struct {
location *ast.Node
code int32
}

type TypeSystemEntity any

type TypeSystemPropertyName int32
Expand Down Expand Up @@ -893,6 +898,7 @@ type Checker struct {
reportedUnreachableNodes collections.Set[*ast.Node]
nonExistentProperties collections.Set[NonExistentPropertyKey]
deferredDiagnosticCallbacks []func()
deprecatedSuggestionKeys collections.Set[deprecatedSuggestionKey]
typeToStringNodebuilder *NodeBuilder

mu sync.Mutex
Expand Down Expand Up @@ -2193,7 +2199,7 @@ func (c *Checker) getSymbol(symbols ast.SymbolTable, name string, meaning ast.Sy
return nil
}

func (c *Checker) checkSourceFile(ctx context.Context, sourceFile *ast.SourceFile, checkUnused bool) {
func (c *Checker) checkSourceFile(ctx context.Context, sourceFile *ast.SourceFile, checkUnused bool, checkDeprecatedProperties bool) {
c.ctx = ctx
links := c.sourceFileLinks.Get(sourceFile)
if !links.typeChecked {
Expand Down Expand Up @@ -2223,6 +2229,10 @@ func (c *Checker) checkSourceFile(ctx context.Context, sourceFile *ast.SourceFil
}
links.unusedChecked = true
}
if checkDeprecatedProperties && !links.deprecatedPropertiesChecked {
c.checkDeprecatedProperties(&links.deprecatedPropertyCheckNodes)
links.deprecatedPropertiesChecked = true
}
if c.isCanceled() {
c.wasCanceled = true
}
Expand Down Expand Up @@ -8374,6 +8384,9 @@ func (c *Checker) checkDeprecatedSignature(sig *Signature, node *ast.Node) {

func (c *Checker) addDeprecatedSuggestionWithSignature(location *ast.Node, declaration *ast.Node, deprecatedEntity string, signatureString string) *ast.Diagnostic {
message := core.IfElse(deprecatedEntity != "", diagnostics.The_signature_0_of_1_is_deprecated, diagnostics.X_0_is_deprecated)
if !c.deprecatedSuggestionKeys.AddIfAbsent(deprecatedSuggestionKey{location: location, code: message.Code()}) {
return nil
}
diagnostic := NewDiagnosticForNode(location, message, signatureString, deprecatedEntity)
return c.addDeprecatedSuggestionWorker([]*ast.Node{declaration}, diagnostic)
}
Expand Down Expand Up @@ -13164,6 +13177,7 @@ func (c *Checker) checkObjectLiteral(node *ast.Node, checkMode CheckMode) *Type
spread := c.emptyObjectType
c.pushCachedContextualType(node)
contextualType := c.getApparentTypeOfContextualType(node, ContextFlagsNone)
c.registerForDeprecatedPropertiesCheck(contextualType, node)
var contextualTypeHasPattern bool
if contextualType != nil {
if pattern := c.patternForType[contextualType]; pattern != nil && (ast.IsObjectBindingPattern(pattern) || ast.IsObjectLiteralExpression(pattern)) {
Expand Down Expand Up @@ -13268,9 +13282,6 @@ func (c *Checker) checkObjectLiteral(node *ast.Node, checkMode CheckMode) *Type
if allPropertiesTable != nil {
allPropertiesTable[prop.Name] = prop
}
if ast.IsIdentifier(memberDecl.Name()) {
c.checkDeprecatedProperty(memberDecl.Name(), contextualType)
}
if contextualType != nil && checkMode&CheckModeInferential != 0 && checkMode&CheckModeSkipContextSensitive == 0 && (ast.IsPropertyAssignment(memberDecl) || ast.IsMethodDeclaration(memberDecl)) && c.isContextSensitive(memberDecl) {
inferenceContext := c.getInferenceContext(node)
// In CheckMode.Inferential we should always have an inference context
Expand Down Expand Up @@ -13355,8 +13366,33 @@ func (c *Checker) checkObjectLiteral(node *ast.Node, checkMode CheckMode) *Type
return createObjectLiteralType()
}

func (c *Checker) registerForDeprecatedPropertiesCheck(contextualType *Type, contextNode *ast.Node) {
if contextualType != nil {
sourceFile := ast.GetSourceFileOfNode(contextNode)
links := c.sourceFileLinks.Get(sourceFile)
links.deprecatedPropertyCheckNodes.Add(contextNode)
}
}

func (c *Checker) checkDeprecatedProperties(contexts *collections.OrderedSet[*ast.Node]) {
for contextNode := range contexts.Values() {
var contextualType *Type
Comment thread
jakebailey marked this conversation as resolved.
Outdated
if ast.IsJsxAttributes(contextNode) {
contextualType = c.getContextualType(contextNode, ContextFlagsNone)
} else {
contextualType = c.getApparentTypeOfContextualType(contextNode, ContextFlagsNone)
}
for _, property := range contextNode.Properties() {
if property.Name() != nil && ast.IsIdentifier(property.Name()) &&
(ast.IsJsxAttribute(property) || ast.IsPropertyAssignment(property) || ast.IsShorthandPropertyAssignment(property) || ast.IsObjectLiteralMethod(property)) {
Comment thread
jakebailey marked this conversation as resolved.
Outdated
c.checkDeprecatedProperty(property.Name(), contextualType)
}
}
}
}

func (c *Checker) checkDeprecatedProperty(name *ast.IdentifierNode, contextualType *Type) {
if contextualType == nil || name == nil {
if contextualType == nil {
return
}
prop := c.getPropertyOfType(contextualType, name.Text())
Expand Down Expand Up @@ -13949,17 +13985,17 @@ func (c *Checker) getCannotFindNameDiagnosticForName(node *ast.Node) *diagnostic
}

func (c *Checker) GetDiagnostics(ctx context.Context, sourceFile *ast.SourceFile) []*ast.Diagnostic {
return c.getDiagnostics(ctx, sourceFile, &c.diagnostics)
return c.getDiagnostics(ctx, sourceFile, &c.diagnostics, false)
}

func (c *Checker) GetSuggestionDiagnostics(ctx context.Context, sourceFile *ast.SourceFile) []*ast.Diagnostic {
return c.getDiagnostics(ctx, sourceFile, &c.suggestionDiagnostics)
return c.getDiagnostics(ctx, sourceFile, &c.suggestionDiagnostics, true)
}

func (c *Checker) getDiagnostics(ctx context.Context, sourceFile *ast.SourceFile, collection *ast.DiagnosticsCollection) []*ast.Diagnostic {
func (c *Checker) getDiagnostics(ctx context.Context, sourceFile *ast.SourceFile, collection *ast.DiagnosticsCollection, checkDeprecatedProperties bool) []*ast.Diagnostic {
c.checkNotCanceled()
checkUnused := c.compilerOptions.NoUnusedLocals.IsTrue() || c.compilerOptions.NoUnusedParameters.IsTrue() || collection == &c.suggestionDiagnostics
c.checkSourceFile(ctx, sourceFile, checkUnused)
c.checkSourceFile(ctx, sourceFile, checkUnused, checkDeprecatedProperties)
if c.wasCanceled {
return nil
}
Expand Down Expand Up @@ -14035,6 +14071,9 @@ func (c *Checker) IsDeprecatedDeclaration(declaration *ast.Node) bool {
}

func (c *Checker) addDeprecatedSuggestion(location *ast.Node, declarations []*ast.Node, deprecatedEntity string) *ast.Diagnostic {
if !c.deprecatedSuggestionKeys.AddIfAbsent(deprecatedSuggestionKey{location: location, code: diagnostics.X_0_is_deprecated.Code()}) {
return nil
}
diagnostic := NewDiagnosticForNode(location, diagnostics.X_0_is_deprecated, deprecatedEntity)
return c.addDeprecatedSuggestionWorker(declarations, diagnostic)
}
Expand Down
4 changes: 1 addition & 3 deletions internal/checker/jsx.go
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,7 @@ func (c *Checker) createJsxAttributesTypeFromAttributesProperty(openingLikeEleme
attributesSymbol = attributes.Symbol()
attributeParent = attributes
contextualType := c.getContextualType(attributes, ContextFlagsNone)
c.registerForDeprecatedPropertiesCheck(contextualType, attributes)
// Create anonymous type from given attributes symbol table.
// @param symbol a symbol of JsxAttributes containing attributes corresponding to attributesTable
// @param attributesTable a symbol table of attributes property
Expand All @@ -756,9 +757,6 @@ func (c *Checker) createJsxAttributesTypeFromAttributesProperty(openingLikeEleme
if attributeDecl.Name().Text() == jsxChildrenPropertyName {
explicitlySpecifyChildrenAttribute = true
}
if ast.IsIdentifier(attributeDecl.Name()) {
c.checkDeprecatedProperty(attributeDecl.Name(), contextualType)
}
if contextualType != nil && checkMode&CheckModeInferential != 0 && checkMode&CheckModeSkipContextSensitive == 0 && c.isContextSensitive(attributeDecl) {
inferenceContext := c.getInferenceContext(attributes)
debug.Assert(inferenceContext != nil)
Expand Down
2 changes: 2 additions & 0 deletions internal/checker/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -396,10 +396,12 @@ type AssertionLinks struct {
type SourceFileLinks struct {
typeChecked bool
unusedChecked bool
deprecatedPropertiesChecked bool
externalHelpersModule *ast.Symbol
requestedExternalEmitHelpers ExternalEmitHelpers
deferredNodes collections.OrderedSet[*ast.Node]
identifierCheckNodes []*ast.Node
deprecatedPropertyCheckNodes collections.OrderedSet[*ast.Node]
localJsxNamespace string
localJsxFragmentNamespace string
localJsxFactory *ast.EntityName
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package fourslash_test

import (
"testing"

"github.com/microsoft/typescript-go/internal/fourslash"
"github.com/microsoft/typescript-go/internal/lsp/lsproto"
"github.com/microsoft/typescript-go/internal/testutil"
)

func TestDeprecatedContextualPropertyOverload(t *testing.T) {
t.Parallel()
defer testutil.RecoverAndFail(t, "Panic on fourslash test")

const content = `interface DeprecatedOptions {
kind: "deprecated";
/** @deprecated */
value: number;
}
interface CurrentOptions {
kind: "current";
value: number;
}
declare function select(options: DeprecatedOptions): void;
declare function select(options: CurrentOptions): void;

select({ kind: "current", value: 1 });

/** @deprecated */
declare const deprecatedValue: number;
select({ kind: "current", value: [|deprecatedValue|] });

interface DeprecatedContainer {
/** @deprecated */
value: number;
}
declare const deprecatedContainer: DeprecatedContainer;
select({ kind: "current", value: deprecatedContainer.[|value|] });

/** @deprecated */
declare function deprecatedCall(): number;
select({ kind: "current", value: [|deprecatedCall|]() });

interface FirstDeprecatedOptions {
kind: "first";
/** @deprecated */
value: number;
}
interface SecondDeprecatedOptions {
kind: "second";
/** @deprecated */
value: number;
}
declare function selectDeprecated(options: FirstDeprecatedOptions): void;
declare function selectDeprecated(options: SecondDeprecatedOptions): void;

selectDeprecated({ kind: "second", [|value|]: 1 });`

f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
defer done()

f.VerifySuggestionDiagnostics(t, []*lsproto.Diagnostic{
{
Code: &lsproto.IntegerOrString{Integer: new(int32(6385))},
Message: lsproto.StringOrMarkupContent{String: new("'deprecatedValue' is deprecated.")},
Tags: &[]lsproto.DiagnosticTag{lsproto.DiagnosticTagDeprecated},
Range: f.Ranges()[0].LSRange,
},
{
Code: &lsproto.IntegerOrString{Integer: new(int32(6385))},
Message: lsproto.StringOrMarkupContent{String: new("'value' is deprecated.")},
Tags: &[]lsproto.DiagnosticTag{lsproto.DiagnosticTagDeprecated},
Range: f.Ranges()[1].LSRange,
},
{
Code: &lsproto.IntegerOrString{Integer: new(int32(6387))},
Message: lsproto.StringOrMarkupContent{String: new("The signature '(): number' of 'deprecatedCall' is deprecated.")},
Tags: &[]lsproto.DiagnosticTag{lsproto.DiagnosticTagDeprecated},
Range: f.Ranges()[2].LSRange,
},
{
Code: &lsproto.IntegerOrString{Integer: new(int32(6385))},
Message: lsproto.StringOrMarkupContent{String: new("'value' is deprecated.")},
Tags: &[]lsproto.DiagnosticTag{lsproto.DiagnosticTagDeprecated},
Range: f.Ranges()[3].LSRange,
},
})
}