From b6364c5745bd7492c9b25014760b11726acdaae2 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Tue, 3 Mar 2026 12:05:51 -0800 Subject: [PATCH 1/7] feat(typescript): add no-deprecated rule --- cmd/rslint/api.go | 17 + internal/config/config.go | 2 + .../rules/no_deprecated/no_deprecated.go | 1897 +++++++++++++ .../rules/no_deprecated/no_deprecated.md | 9 + .../rules/no_deprecated/no_deprecated_test.go | 141 + packages/rslint-test-tools/rstest.config.mts | 2 +- .../__snapshots__/no-deprecated.test.ts.snap | 2365 +++++++++++++++++ .../rules/no-deprecated.test.ts | 120 +- 8 files changed, 4507 insertions(+), 46 deletions(-) create mode 100644 internal/plugins/typescript/rules/no_deprecated/no_deprecated.go create mode 100644 internal/plugins/typescript/rules/no_deprecated/no_deprecated.md create mode 100644 internal/plugins/typescript/rules/no_deprecated/no_deprecated_test.go create mode 100644 packages/rslint-test-tools/tests/typescript-eslint/rules/__snapshots__/no-deprecated.test.ts.snap diff --git a/cmd/rslint/api.go b/cmd/rslint/api.go index 98fc76b51..e8d9a03d3 100644 --- a/cmd/rslint/api.go +++ b/cmd/rslint/api.go @@ -126,6 +126,23 @@ func (h *IPCHandler) HandleLint(req api.LintRequest) (*api.LintResponse, error) }) } } + } else { + // Default to enabling all rules with empty options when none were specified. + for _, r := range rslintconfig.GlobalRuleRegistry.GetAllRules() { + rulesWithOptions = append(rulesWithOptions, RuleWithOption{ + rule: r, + option: []interface{}{}, + }) + } + } + if len(rulesWithOptions) == 0 { + // Default to enabling all rules with empty options when none were specified. + for _, r := range rslintconfig.GlobalRuleRegistry.GetAllRules() { + rulesWithOptions = append(rulesWithOptions, RuleWithOption{ + rule: r, + option: []interface{}{}, + }) + } } // Create compiler host diff --git a/internal/config/config.go b/internal/config/config.go index 2fb18ab35..5f1c2e371 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -29,6 +29,7 @@ import ( "github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_array_delete" "github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_base_to_string" "github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_confusing_void_expression" + "github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_deprecated" "github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_duplicate_enum_values" "github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_duplicate_type_constituents" "github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_empty_function" @@ -395,6 +396,7 @@ func registerAllTypeScriptEslintPluginRules() { GlobalRuleRegistry.Register("@typescript-eslint/no-array-delete", no_array_delete.NoArrayDeleteRule) GlobalRuleRegistry.Register("@typescript-eslint/no-base-to-string", no_base_to_string.NoBaseToStringRule) GlobalRuleRegistry.Register("@typescript-eslint/no-confusing-void-expression", no_confusing_void_expression.NoConfusingVoidExpressionRule) + GlobalRuleRegistry.Register("@typescript-eslint/no-deprecated", no_deprecated.NoDeprecatedRule) GlobalRuleRegistry.Register("@typescript-eslint/no-duplicate-enum-values", no_duplicate_enum_values.NoDuplicateEnumValuesRule) GlobalRuleRegistry.Register("@typescript-eslint/no-duplicate-type-constituents", no_duplicate_type_constituents.NoDuplicateTypeConstituentsRule) GlobalRuleRegistry.Register("@typescript-eslint/no-explicit-any", no_explicit_any.NoExplicitAnyRule) diff --git a/internal/plugins/typescript/rules/no_deprecated/no_deprecated.go b/internal/plugins/typescript/rules/no_deprecated/no_deprecated.go new file mode 100644 index 000000000..23d6b5f2f --- /dev/null +++ b/internal/plugins/typescript/rules/no_deprecated/no_deprecated.go @@ -0,0 +1,1897 @@ +package no_deprecated + +import ( + "context" + "regexp" + "strconv" + "strings" + + "github.com/microsoft/typescript-go/shim/ast" + "github.com/microsoft/typescript-go/shim/checker" + "github.com/microsoft/typescript-go/shim/core" + "github.com/web-infra-dev/rslint/internal/rule" + "github.com/web-infra-dev/rslint/internal/utils" +) + +var deprecatedReasonPattern = regexp.MustCompile(`(?s)@deprecated\s*([\s\S]*?)\*/`) + +const ( + diagnosticCodeSecondEntityName = 6387 + declarationReasonSearchWindowBytes = 512 + maxConstantPropertyResolveDepth = 8 +) + +func buildDeprecatedMessage(name string) rule.RuleMessage { + return rule.RuleMessage{ + Id: "deprecated", + Description: "`" + name + "` is deprecated.", + } +} + +func buildDeprecatedWithReasonMessage(name string, reason string) rule.RuleMessage { + return rule.RuleMessage{ + Id: "deprecatedWithReason", + Description: "`" + name + "` is deprecated. " + reason, + } +} + +func isNodeCalleeOfParent(node *ast.Node) bool { + if node == nil || node.Parent == nil { + return false + } + switch node.Parent.Kind { + case ast.KindNewExpression: + newExpr := node.Parent.AsNewExpression() + return newExpr != nil && newExpr.Expression == node + case ast.KindCallExpression: + callExpr := node.Parent.AsCallExpression() + return callExpr != nil && callExpr.Expression == node + case ast.KindTaggedTemplateExpression: + taggedTemplate := node.Parent.AsTaggedTemplateExpression() + return taggedTemplate != nil && taggedTemplate.Tag == node + case ast.KindJsxOpeningElement: + jsxOpening := node.Parent.AsJsxOpeningElement() + return jsxOpening != nil && jsxOpening.TagName == node + case ast.KindJsxSelfClosingElement: + jsxSelfClosing := node.Parent.AsJsxSelfClosingElement() + return jsxSelfClosing != nil && jsxSelfClosing.TagName == node + default: + return false + } +} + +func getCallLikeNode(node *ast.Node) *ast.Node { + callee := node + for { + if callee == nil || callee.Parent == nil || callee.Parent.Kind != ast.KindPropertyAccessExpression { + break + } + parentAccess := callee.Parent.AsPropertyAccessExpression() + if parentAccess == nil || parentAccess.Name() == nil || parentAccess.Name().AsNode() != callee { + break + } + callee = callee.Parent + } + if isNodeCalleeOfParent(callee) { + return callee + } + return nil +} + +func getReportedNodeName(node *ast.Node) string { + if node == nil { + return "" + } + if node.Kind == ast.KindSuperKeyword { + return "super" + } + if node.Kind == ast.KindPrivateIdentifier { + privateIdentifier := node.AsPrivateIdentifier() + if privateIdentifier != nil { + return "#" + privateIdentifier.Text + } + } + return node.Text() +} + +func getJsDocDeprecationFromNode(node *ast.Node) string { + if node == nil { + return "" + } + jsdocs := node.JSDoc(nil) + for _, jsdoc := range jsdocs { + tags := jsdoc.AsJSDoc().Tags + if tags == nil { + continue + } + for _, tagNode := range tags.Nodes { + if !ast.IsJSDocDeprecatedTag(tagNode) { + continue + } + deprecatedTag := tagNode.AsJSDocDeprecatedTag() + if deprecatedTag != nil && deprecatedTag.Comment != nil && len(deprecatedTag.Comment.Nodes) > 0 { + var text strings.Builder + for _, commentNode := range deprecatedTag.Comment.Nodes { + text.WriteString(commentNode.Text()) + } + return strings.TrimSpace(text.String()) + } + return "" + } + } + return "" +} + +func hasDeprecatedTag(node *ast.Node) bool { + if node == nil { + return false + } + jsdocs := node.JSDoc(nil) + for _, jsdoc := range jsdocs { + tags := jsdoc.AsJSDoc().Tags + if tags == nil { + continue + } + for _, tagNode := range tags.Nodes { + if ast.IsJSDocDeprecatedTag(tagNode) { + return true + } + } + } + return false +} + +func hasDeprecatedTagInSource(node *ast.Node) bool { + if node == nil { + return false + } + sourceFile := ast.GetSourceFileOfNode(node) + if sourceFile == nil { + return false + } + text := sourceFile.Text() + if text == "" { + return false + } + + anchor := node.Pos() + if node.Kind == ast.KindVariableDeclaration && node.Parent != nil && node.Parent.Parent != nil { + anchor = node.Parent.Parent.Pos() + } + if anchor <= 0 || anchor > len(text) { + return false + } + // Walk backwards over whitespace to find a directly preceding JSDoc comment. + i := anchor - 1 + for i >= 0 { + ch := text[i] + if ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' { + i-- + continue + } + break + } + if i < 1 || text[i] != '/' || text[i-1] != '*' { + return false + } + commentEnd := i + 1 + commentStart := strings.LastIndex(text[:commentEnd], "/**") + if commentStart == -1 { + return false + } + comment := text[commentStart:commentEnd] + if strings.Contains(comment[:len(comment)-2], "*/") { + return false + } + return deprecatedReasonPattern.MatchString(comment) +} + +func getJsDocDeprecation(typeChecker *checker.Checker, symbol *ast.Symbol) (bool, string) { + if typeChecker == nil || symbol == nil { + return false, "" + } + for _, decl := range symbol.Declarations { + if decl == nil { + continue + } + if decl.Kind == ast.KindBindingElement { + continue + } + if typeChecker.IsDeprecatedDeclaration(decl) || hasDeprecatedTag(decl) || hasDeprecatedTagInSource(decl) { + reason := getJsDocDeprecationFromNode(decl) + if reason == "" { + reason = deprecatedReasonFromDeclaration(decl) + } + return true, reason + } + } + if symbol.ValueDeclaration != nil { + if symbol.ValueDeclaration.Kind != ast.KindBindingElement && + (typeChecker.IsDeprecatedDeclaration(symbol.ValueDeclaration) || hasDeprecatedTag(symbol.ValueDeclaration) || hasDeprecatedTagInSource(symbol.ValueDeclaration)) { + reason := getJsDocDeprecationFromNode(symbol.ValueDeclaration) + if reason == "" { + reason = deprecatedReasonFromDeclaration(symbol.ValueDeclaration) + } + return true, reason + } + } + return false, "" +} + +func searchForDeprecationInAliasesChain( + typeChecker *checker.Checker, + symbol *ast.Symbol, + checkDeprecationsOfAliasedSymbol bool, +) (bool, string) { + if typeChecker == nil || symbol == nil { + return false, "" + } + if symbol.Flags&ast.SymbolFlagsAlias == 0 { + if checkDeprecationsOfAliasedSymbol { + return getJsDocDeprecation(typeChecker, symbol) + } + return false, "" + } + if isDeprecated, reason := getJsDocDeprecation(typeChecker, symbol); isDeprecated { + return true, reason + } + if !checkDeprecationsOfAliasedSymbol { + return false, "" + } + aliasedSymbol := typeChecker.GetAliasedSymbol(symbol) + if aliasedSymbol == nil { + return false, "" + } + return getJsDocDeprecation(typeChecker, aliasedSymbol) +} + +func stripQuotes(text string) string { + text = strings.TrimSpace(text) + if len(text) >= 2 { + if (strings.HasPrefix(text, "'") && strings.HasSuffix(text, "'")) || + (strings.HasPrefix(text, "\"") && strings.HasSuffix(text, "\"")) || + (strings.HasPrefix(text, "`") && strings.HasSuffix(text, "`")) { + return text[1 : len(text)-1] + } + } + return text +} + +func normalizeComparableName(text string) string { + return strings.TrimPrefix(stripQuotes(text), "#") +} + +func diagnosticEntityName(diagnostic *ast.Diagnostic) string { + if diagnostic == nil { + return "" + } + args := diagnostic.MessageArgs() + if len(args) == 0 { + return "" + } + // Diagnostic 6387 reports the relevant entity name in the second argument. + if diagnostic.Code() == diagnosticCodeSecondEntityName && len(args) >= 2 { + return stripQuotes(args[1]) + } + return stripQuotes(args[0]) +} + +func sourceSpanText(sourceFile *ast.SourceFile, pos int, end int) string { + if sourceFile == nil { + return "" + } + text := sourceFile.Text() + if pos < 0 || end > len(text) || pos >= end { + return "" + } + return text[pos:end] +} + +func cleanupDeprecatedReason(text string) string { + text = strings.TrimSpace(text) + if text == "" { + return "" + } + text = strings.TrimPrefix(text, ":") + text = strings.TrimSpace(text) + text = strings.TrimSuffix(text, "*/") + text = strings.TrimSpace(text) + lines := strings.Split(text, "\n") + parts := make([]string, 0, len(lines)) + for _, line := range lines { + trimmed := strings.TrimSpace(line) + trimmed = strings.TrimPrefix(trimmed, "*") + trimmed = strings.TrimSpace(trimmed) + if trimmed != "" { + parts = append(parts, trimmed) + } + } + return strings.TrimSpace(strings.Join(parts, " ")) +} + +func deprecatedReasonFromDiagnostic(diagnostic *ast.Diagnostic) string { + if diagnostic == nil { + return "" + } + for _, related := range diagnostic.RelatedInformation() { + if related == nil || related.File() == nil { + continue + } + text := sourceSpanText(related.File(), related.Pos(), related.End()) + if text == "" { + continue + } + at := strings.Index(text, "@deprecated") + if at < 0 { + continue + } + reason := cleanupDeprecatedReason(text[at+len("@deprecated"):]) + if reason != "" { + return reason + } + } + return "" +} + +func deprecatedReasonFromDeclaration(declaration *ast.Node) string { + if declaration == nil { + return "" + } + sourceFile := ast.GetSourceFileOfNode(declaration) + if sourceFile == nil { + return "" + } + text := sourceFile.Text() + if text == "" { + return "" + } + + start := declaration.Pos() + if start < 0 || start > len(text) { + return "" + } + windowStart := start - declarationReasonSearchWindowBytes + if windowStart < 0 { + windowStart = 0 + } + windowEnd := declaration.End() + if windowEnd < start { + windowEnd = start + } + if windowEnd > len(text) { + windowEnd = len(text) + } + snippet := text[windowStart:windowEnd] + matches := deprecatedReasonPattern.FindAllStringSubmatch(snippet, -1) + if len(matches) == 0 { + return "" + } + lastMatch := matches[len(matches)-1] + if len(lastMatch) < 2 { + return "" + } + return cleanupDeprecatedReason(lastMatch[1]) +} + +type noDeprecatedAllowEntry struct { + From string + Name string + Package string +} + +func parseAllowEntries(options any) []noDeprecatedAllowEntry { + entries := []noDeprecatedAllowEntry{} + var optionMap map[string]interface{} + switch value := options.(type) { + case map[string]interface{}: + optionMap = value + case []interface{}: + if len(value) > 0 { + if parsedMap, ok := value[0].(map[string]interface{}); ok { + optionMap = parsedMap + } + } + } + if optionMap == nil { + return entries + } + rawAllowValue, exists := optionMap["allow"] + if !exists { + return entries + } + rawAllow, ok := rawAllowValue.([]interface{}) + if !ok { + return entries + } + for _, raw := range rawAllow { + switch value := raw.(type) { + case string: + entries = append(entries, noDeprecatedAllowEntry{ + Name: value, + }) + case map[string]interface{}: + entry := noDeprecatedAllowEntry{} + if name, ok := value["name"].(string); ok { + entry.Name = name + } + if from, ok := value["from"].(string); ok { + entry.From = from + } + if pkg, ok := value["package"].(string); ok { + entry.Package = pkg + } + if entry.Name != "" { + entries = append(entries, entry) + } + } + } + return entries +} + +func diagnosticNode(sourceFile *ast.SourceFile, position int, end int) *ast.Node { + if sourceFile == nil { + return nil + } + candidates := []int{ + position, + end - 1, + (position + end) / 2, + position + 1, + position - 1, + } + for _, candidate := range candidates { + if candidate < 0 || candidate >= len(sourceFile.Text()) { + continue + } + if node := ast.GetNodeAtPosition(sourceFile, candidate, true); node != nil { + return node + } + } + return nil +} + +func symbolHierarchyNames(symbol *ast.Symbol) map[string]bool { + names := map[string]bool{} + for current := symbol; current != nil; current = current.Parent { + if current.Name == "" { + continue + } + names[current.Name] = true + unquoted := strings.Trim(current.Name, "\"'") + if unquoted != "" { + names[unquoted] = true + } + } + return names +} + +func declarationInCurrentFile(symbol *ast.Symbol, sourceFile *ast.SourceFile) bool { + if symbol == nil || sourceFile == nil { + return false + } + for _, declaration := range symbol.Declarations { + if declaration == nil { + continue + } + if ast.GetSourceFileOfNode(declaration) == sourceFile { + return true + } + } + return false +} + +func packageMatchesSymbol(entryPackage string, symbol *ast.Symbol) bool { + if entryPackage == "" || symbol == nil { + return false + } + hierarchy := symbolHierarchyNames(symbol) + if hierarchy[entryPackage] || hierarchy["@types/"+entryPackage] { + return true + } + for current := symbol; current != nil; current = current.Parent { + for _, declaration := range current.Declarations { + sourceFile := ast.GetSourceFileOfNode(declaration) + if sourceFile == nil { + continue + } + fileName := sourceFile.FileName() + if strings.Contains(fileName, "/node_modules/"+entryPackage+"/") || + strings.Contains(fileName, "/node_modules/@types/"+entryPackage+"/") { + return true + } + } + } + return false +} + +func walkAst(node *ast.Node, visitor func(*ast.Node) bool) bool { + if node == nil { + return false + } + if visitor(node) { + return true + } + shouldStop := false + node.ForEachChild(func(child *ast.Node) bool { + if walkAst(child, visitor) { + shouldStop = true + return true + } + return false + }) + return shouldStop +} + +func moduleSpecifierText(node *ast.Node) string { + if node == nil { + return "" + } + switch node.Kind { + case ast.KindStringLiteral: + stringLiteral := node.AsStringLiteral() + if stringLiteral != nil { + return stringLiteral.Text + } + case ast.KindNoSubstitutionTemplateLiteral: + templateLiteral := node.AsNoSubstitutionTemplateLiteral() + if templateLiteral != nil { + return templateLiteral.Text + } + } + return stripQuotes(strings.TrimSpace(node.Text())) +} + +func nodeNameText(node *ast.Node) string { + if node == nil { + return "" + } + switch node.Kind { + case ast.KindIdentifier: + identifier := node.AsIdentifier() + if identifier == nil { + return "" + } + return identifier.Text + case ast.KindPrivateIdentifier: + privateIdentifier := node.AsPrivateIdentifier() + if privateIdentifier == nil { + return "" + } + return "#" + privateIdentifier.Text + case ast.KindStringLiteral: + stringLiteral := node.AsStringLiteral() + if stringLiteral == nil { + return "" + } + return stringLiteral.Text + case ast.KindNumericLiteral: + numericLiteral := node.AsNumericLiteral() + if numericLiteral == nil { + return "" + } + return numericLiteral.Text + case ast.KindComputedPropertyName: + computedPropertyName := node.AsComputedPropertyName() + if computedPropertyName == nil || computedPropertyName.Expression == nil { + return "" + } + return nodeNameText(computedPropertyName.Expression) + case ast.KindBindingElement: + bindingElement := node.AsBindingElement() + if bindingElement == nil || bindingElement.Name() == nil { + return "" + } + return nodeNameText(bindingElement.Name()) + case ast.KindSuperKeyword: + return "super" + case ast.KindObjectBindingPattern, ast.KindArrayBindingPattern: + // Binding patterns do not represent a single declaration name. + return "" + default: + // Avoid calling Node.Text on unsupported kinds (for example binding patterns), + // which can panic in typescript-go internals. + return "" + } +} + +func importedNameMatches(targetName string, node *ast.Node) bool { + if targetName == "" || node == nil { + return false + } + return normalizeComparableName(targetName) == normalizeComparableName(nodeNameText(node)) +} + +func staticImportContainsName(importDeclaration *ast.ImportDeclaration, targetName string) bool { + if importDeclaration == nil || importDeclaration.ImportClause == nil || targetName == "" { + return false + } + importClause := importDeclaration.ImportClause.AsImportClause() + if importClause == nil { + return false + } + if importClause.Name() != nil && importedNameMatches(targetName, importClause.Name()) { + return true + } + if importClause.NamedBindings == nil { + return false + } + if importClause.NamedBindings.Kind == ast.KindNamespaceImport { + namespaceImport := importClause.NamedBindings.AsNamespaceImport() + if namespaceImport != nil && namespaceImport.Name() != nil && importedNameMatches(targetName, namespaceImport.Name()) { + return true + } + return false + } + if importClause.NamedBindings.Kind != ast.KindNamedImports { + return false + } + namedImports := importClause.NamedBindings.AsNamedImports() + if namedImports == nil || namedImports.Elements == nil { + return false + } + for _, elementNode := range namedImports.Elements.Nodes { + importSpecifier := elementNode.AsImportSpecifier() + if importSpecifier == nil { + continue + } + if importSpecifier.Name() != nil && importedNameMatches(targetName, importSpecifier.Name()) { + return true + } + if importSpecifier.PropertyName != nil && importedNameMatches(targetName, importSpecifier.PropertyName) { + return true + } + } + return false +} + +func isImportCallFromPackage(initializer *ast.Node, pkg string) bool { + if initializer == nil || pkg == "" { + return false + } + current := ast.SkipParentheses(initializer) + if current == nil { + return false + } + if current.Kind == ast.KindAwaitExpression { + awaitExpression := current.AsAwaitExpression() + if awaitExpression == nil { + return false + } + current = ast.SkipParentheses(awaitExpression.Expression) + } + if current == nil || current.Kind != ast.KindCallExpression { + return false + } + callExpression := current.AsCallExpression() + if callExpression == nil || callExpression.Expression == nil || callExpression.Arguments == nil || len(callExpression.Arguments.Nodes) == 0 { + return false + } + callee := ast.SkipParentheses(callExpression.Expression) + if callee == nil || callee.Kind != ast.KindImportKeyword { + return false + } + return moduleSpecifierText(callExpression.Arguments.Nodes[0]) == pkg +} + +func dynamicImportBindingContainsName(nameNode *ast.Node, targetName string) bool { + if nameNode == nil || targetName == "" || nameNode.Kind != ast.KindObjectBindingPattern { + return false + } + objectBindingPattern := nameNode.AsBindingPattern() + if objectBindingPattern == nil || objectBindingPattern.Elements == nil { + return false + } + for _, elementNode := range objectBindingPattern.Elements.Nodes { + bindingElement := elementNode.AsBindingElement() + if bindingElement == nil { + continue + } + propertyName := bindingElementPropertyName(bindingElement) + if propertyName != "" && normalizeComparableName(propertyName) == normalizeComparableName(targetName) { + return true + } + name := bindingElement.Name() + if name != nil && importedNameMatches(targetName, name) { + return true + } + } + return false +} + +func nameImportedFromPackage(sourceFile *ast.SourceFile, name string, pkg string) bool { + if sourceFile == nil || name == "" || pkg == "" { + return false + } + if sourceFile.Statements == nil { + return false + } + for _, statement := range sourceFile.Statements.Nodes { + if statement == nil { + continue + } + if statement.Kind == ast.KindImportDeclaration { + importDeclaration := statement.AsImportDeclaration() + if importDeclaration == nil || importDeclaration.ModuleSpecifier == nil { + continue + } + if moduleSpecifierText(importDeclaration.ModuleSpecifier) != pkg { + continue + } + if staticImportContainsName(importDeclaration, name) { + return true + } + continue + } + if walkAst(statement, func(node *ast.Node) bool { + if node == nil || node.Kind != ast.KindVariableDeclaration { + return false + } + variableDeclaration := node.AsVariableDeclaration() + if variableDeclaration == nil || variableDeclaration.Initializer == nil || variableDeclaration.Name() == nil { + return false + } + if !isImportCallFromPackage(variableDeclaration.Initializer, pkg) { + return false + } + return dynamicImportBindingContainsName(variableDeclaration.Name(), name) + }) { + return true + } + } + // Fallback for parser edge cases where import binding nodes are not exposed as expected. + sourceText := sourceFile.Text() + if sourceText == "" { + return false + } + namePattern := regexp.QuoteMeta(name) + pkgPattern := regexp.QuoteMeta(pkg) + staticImportPattern := regexp.MustCompile(`(?s)import\s*\{[^}]*\b` + namePattern + `\b[^}]*\}\s*from\s*['"]` + pkgPattern + `['"]`) + if staticImportPattern.MatchString(sourceText) { + return true + } + dynamicImportPattern := regexp.MustCompile(`(?s)\{[^}]*\b` + namePattern + `\b[^}]*\}\s*=\s*(?:await\s+)?import\(\s*['"]` + pkgPattern + `['"]\s*\)`) + return dynamicImportPattern.MatchString(sourceText) +} + +func allowEntryMatches(entry noDeprecatedAllowEntry, diagnosticName string, symbol *ast.Symbol, sourceFile *ast.SourceFile) bool { + if entry.Name != "" { + if normalizeComparableName(diagnosticName) == normalizeComparableName(entry.Name) { + // direct match + } else if symbol != nil { + hierarchy := symbolHierarchyNames(symbol) + nameMatched := false + for hierarchyName := range hierarchy { + if normalizeComparableName(hierarchyName) == normalizeComparableName(entry.Name) { + nameMatched = true + break + } + } + if !nameMatched { + return false + } + } else { + return false + } + } + + switch entry.From { + case "": + return true + case "file": + if symbol == nil { + return entry.Name != "" && normalizeComparableName(diagnosticName) == normalizeComparableName(entry.Name) + } + return declarationInCurrentFile(symbol, sourceFile) + case "package": + if packageMatchesSymbol(entry.Package, symbol) { + return true + } + return nameImportedFromPackage(sourceFile, entry.Name, entry.Package) + default: + return false + } +} + +func shouldAllowDiagnostic(entries []noDeprecatedAllowEntry, diagnosticName string, symbol *ast.Symbol, sourceFile *ast.SourceFile) bool { + for _, entry := range entries { + if allowEntryMatches(entry, diagnosticName, symbol, sourceFile) { + return true + } + } + return false +} + +func symbolAtLocation(typeChecker *checker.Checker, node *ast.Node) *ast.Symbol { + if typeChecker == nil || node == nil { + return nil + } + if symbol := typeChecker.GetSymbolAtLocation(node); symbol != nil { + return symbol + } + switch node.Kind { + case ast.KindPropertyAccessExpression: + access := node.AsPropertyAccessExpression() + if access != nil && access.Name() != nil { + if symbol := typeChecker.GetSymbolAtLocation(access.Name()); symbol != nil { + return symbol + } + } + case ast.KindElementAccessExpression: + access := node.AsElementAccessExpression() + if access != nil && access.ArgumentExpression != nil { + if symbol := typeChecker.GetSymbolAtLocation(access.ArgumentExpression); symbol != nil { + return symbol + } + } + case ast.KindJsxOpeningElement: + opening := node.AsJsxOpeningElement() + if opening != nil && opening.TagName != nil { + if symbol := typeChecker.GetSymbolAtLocation(opening.TagName); symbol != nil { + return symbol + } + } + case ast.KindJsxClosingElement: + closing := node.AsJsxClosingElement() + if closing != nil && closing.TagName != nil { + if symbol := typeChecker.GetSymbolAtLocation(closing.TagName); symbol != nil { + return symbol + } + } + case ast.KindJsxSelfClosingElement: + selfClosing := node.AsJsxSelfClosingElement() + if selfClosing != nil && selfClosing.TagName != nil { + if symbol := typeChecker.GetSymbolAtLocation(selfClosing.TagName); symbol != nil { + return symbol + } + } + } + for parent := node.Parent; parent != nil; parent = parent.Parent { + if symbol := typeChecker.GetSymbolAtLocation(parent); symbol != nil { + return symbol + } + if parent.Kind == ast.KindPropertyAccessExpression { + access := parent.AsPropertyAccessExpression() + if access != nil && access.Name() != nil { + if symbol := typeChecker.GetSymbolAtLocation(access.Name()); symbol != nil { + return symbol + } + } + } + } + return nil +} + +func getCallLikeDeprecation(ctx rule.RuleContext, node *ast.Node) (bool, string) { + if ctx.TypeChecker == nil || node == nil || node.Parent == nil { + return false, "" + } + signature := checker.Checker_getResolvedSignature(ctx.TypeChecker, node.Parent, nil, checker.CheckModeNormal) + if signature == nil { + return false, "" + } + signatureDecl := signature.Declaration() + if signatureDecl != nil && (ctx.TypeChecker.IsDeprecatedDeclaration(signatureDecl) || hasDeprecatedTag(signatureDecl)) { + reason := getJsDocDeprecationFromNode(signatureDecl) + return true, reason + } + symbol := ctx.TypeChecker.GetSymbolAtLocation(node) + if symbol == nil { + return false, "" + } + aliasedSymbol := symbol + if symbol.Flags&ast.SymbolFlagsAlias != 0 { + aliasedSymbol = ctx.TypeChecker.GetAliasedSymbol(symbol) + } + var symbolDeclarationKind ast.Kind + if aliasedSymbol != nil && len(aliasedSymbol.Declarations) > 0 && aliasedSymbol.Declarations[0] != nil { + symbolDeclarationKind = aliasedSymbol.Declarations[0].Kind + } + if symbolDeclarationKind != ast.KindMethodDeclaration && + symbolDeclarationKind != ast.KindFunctionDeclaration && + symbolDeclarationKind != ast.KindMethodSignature { + return searchForDeprecationInAliasesChain(ctx.TypeChecker, symbol, true) + } + isDeprecated, reason := searchForDeprecationInAliasesChain(ctx.TypeChecker, symbol, false) + if isDeprecated { + return true, reason + } + if signatureDecl == nil && aliasedSymbol != nil { + return getJsDocDeprecation(ctx.TypeChecker, aliasedSymbol) + } + return false, "" +} + +func getJsxAttributeDeprecation(ctx rule.RuleContext, elementNode *ast.Node, propertyName string) (bool, string) { + if ctx.TypeChecker == nil || elementNode == nil || propertyName == "" { + return false, "" + } + var tagName *ast.Node + switch elementNode.Kind { + case ast.KindJsxSelfClosingElement: + tagName = elementNode.AsJsxSelfClosingElement().TagName + case ast.KindJsxOpeningElement: + tagName = elementNode.AsJsxOpeningElement().TagName + } + if tagName == nil { + return false, "" + } + contextualType := checker.Checker_getContextualType(ctx.TypeChecker, tagName, checker.ContextFlagsNone) + if contextualType == nil { + return false, "" + } + symbol := checker.Checker_getPropertyOfType(ctx.TypeChecker, contextualType, propertyName) + return getJsDocDeprecation(ctx.TypeChecker, symbol) +} + +func bindingElementPropertyNameFromNode(node *ast.Node) string { + if node == nil || node.Kind != ast.KindBindingElement { + return "" + } + bindingElement := node.AsBindingElement() + return bindingElementPropertyName(bindingElement) +} + +func getBindingPatternSourceType(ctx rule.RuleContext, bindingPattern *ast.Node, seen map[*ast.Node]bool) *checker.Type { + if ctx.TypeChecker == nil || bindingPattern == nil { + return nil + } + current := bindingPattern + if current.Kind == ast.KindArrayBindingPattern { + parentSourceType := getBindingPatternSourceType(ctx, current.Parent, seen) + if parentSourceType != nil && checker.Checker_isArrayOrTupleType(ctx.TypeChecker, parentSourceType) { + typeArgs := checker.Checker_getTypeArguments(ctx.TypeChecker, parentSourceType) + if len(typeArgs) > 0 && typeArgs[0] != nil { + return typeArgs[0] + } + } + } + for current != nil { + if seen[current] { + return nil + } + seen[current] = true + switch current.Kind { + case ast.KindVariableDeclaration: + varDecl := current.AsVariableDeclaration() + if varDecl != nil && varDecl.Initializer != nil { + return utils.GetConstrainedTypeAtLocation(ctx.TypeChecker, varDecl.Initializer) + } + return nil + case ast.KindParameter: + parameter := current.AsParameterDeclaration() + if parameter == nil { + return nil + } + if parameter.Type != nil { + return ctx.TypeChecker.GetTypeAtLocation(parameter.Type) + } + if parameter.Initializer != nil { + return utils.GetConstrainedTypeAtLocation(ctx.TypeChecker, parameter.Initializer) + } + return nil + case ast.KindBindingElement: + bindingElem := current.AsBindingElement() + if bindingElem == nil { + return nil + } + if current.Parent == nil { + return nil + } + parentPattern := current.Parent + parentSourceType := getBindingPatternSourceType(ctx, parentPattern, seen) + if parentSourceType == nil { + return nil + } + propertyName := bindingElementPropertyName(bindingElem) + if propertyName == "" { + return nil + } + property := checker.Checker_getPropertyOfType(ctx.TypeChecker, parentSourceType, propertyName) + if property == nil { + return nil + } + return ctx.TypeChecker.GetTypeOfSymbolAtLocation(property, current) + case ast.KindArrayBindingPattern: + parentSourceType := getBindingPatternSourceType(ctx, current.Parent, seen) + if parentSourceType == nil { + return nil + } + property := checker.Checker_getPropertyOfType(ctx.TypeChecker, parentSourceType, "0") + if property != nil { + return ctx.TypeChecker.GetTypeOfSymbolAtLocation(property, current) + } + return parentSourceType + case ast.KindObjectBindingPattern: + current = current.Parent + continue + } + current = current.Parent + } + return nil +} + +func getDeprecationReason(ctx rule.RuleContext, node *ast.Node) (bool, string) { + if ctx.TypeChecker == nil || node == nil { + return false, "" + } + callLikeNode := getCallLikeNode(node) + if callLikeNode != nil { + return getCallLikeDeprecation(ctx, callLikeNode) + } + if node.Parent != nil && node.Parent.Kind == ast.KindJsxAttribute && node.Kind != ast.KindSuperKeyword { + if node.Parent.Parent != nil && node.Parent.Parent.Parent != nil { + return getJsxAttributeDeprecation(ctx, node.Parent.Parent.Parent, node.Text()) + } + } + if node.Parent != nil && node.Kind != ast.KindSuperKeyword { + parent := node.Parent + if parent.Kind == ast.KindBindingElement { + bindingPattern := parent.Parent + if bindingPattern != nil && (bindingPattern.Kind == ast.KindObjectBindingPattern || bindingPattern.Kind == ast.KindArrayBindingPattern) { + sourceType := getBindingPatternSourceType(ctx, bindingPattern, map[*ast.Node]bool{}) + if sourceType == nil && bindingPattern.Kind == ast.KindObjectBindingPattern { + sourceType = ctx.TypeChecker.GetTypeAtLocation(bindingPattern) + } + if sourceType == nil { + sourceType = ctx.TypeChecker.GetTypeAtLocation(bindingPattern) + } + if sourceType != nil { + bindingElement := parent.AsBindingElement() + bindingNode := node + if bindingElement != nil && bindingElement.PropertyName != nil { + bindingNode = bindingElement.PropertyName + } + propertyName := "" + if bindingPattern.Kind == ast.KindArrayBindingPattern { + if bindingElement != nil { + if index, ok := bindingElementIndex(bindingElement); ok { + propertyName = strconv.Itoa(index) + } + } + } else { + if bindingName := bindingElementPropertyNameFromNode(parent); bindingName != "" { + propertyName = bindingName + } + if propertyName == "" && bindingElement != nil && bindingElement.PropertyName != nil { + if resolvedName, ok := resolveConstantPropertyName(ctx, bindingElement.PropertyName, 0, map[*ast.Symbol]bool{}); ok { + propertyName = resolvedName + } + } + if propertyName == "" { + propertyName = node.Text() + } + } + if propertyName != "" { + property := checker.Checker_getPropertyOfType(ctx.TypeChecker, sourceType, propertyName) + if isDeprecated, reason := getJsDocDeprecation(ctx.TypeChecker, property); isDeprecated { + return true, reason + } + if propertySymbol := ctx.TypeChecker.GetSymbolAtLocation(bindingNode); propertySymbol != nil { + if propertySymbol.ValueDeclaration != nil && propertySymbol.ValueDeclaration.Kind == ast.KindBindingElement { + propertySymbol = nil + } + if isDeprecated, reason := searchForDeprecationInAliasesChain(ctx.TypeChecker, propertySymbol, true); isDeprecated { + return true, reason + } + if isDeprecated, reason := getJsDocDeprecation(ctx.TypeChecker, propertySymbol); isDeprecated { + return true, reason + } + } + } + } + } + } + if parent.Kind == ast.KindShorthandPropertyAssignment && parent.Parent != nil { + parentType := ctx.TypeChecker.GetTypeAtLocation(parent.Parent) + if parentType != nil { + propertySymbol := ctx.TypeChecker.GetSymbolAtLocation(node) + property := checker.Checker_getPropertyOfType(ctx.TypeChecker, parentType, node.Text()) + if isDeprecated, reason := searchForDeprecationInAliasesChain(ctx.TypeChecker, propertySymbol, true); isDeprecated { + return true, reason + } + if isDeprecated, reason := getJsDocDeprecation(ctx.TypeChecker, property); isDeprecated { + return true, reason + } + if isDeprecated, reason := getJsDocDeprecation(ctx.TypeChecker, propertySymbol); isDeprecated { + return true, reason + } + } + } + } + return searchForDeprecationInAliasesChain(ctx.TypeChecker, ctx.TypeChecker.GetSymbolAtLocation(node), true) +} + +func propertyAccessForDiagnosticRange(node *ast.Node, pos int, end int) *ast.PropertyAccessExpression { + for current := node; current != nil; current = current.Parent { + if current.Kind != ast.KindPropertyAccessExpression { + continue + } + access := current.AsPropertyAccessExpression() + if access == nil || access.Name() == nil { + continue + } + nameNode := access.Name() + if nameNode.Pos() == pos && nameNode.End() == end { + return access + } + if pos <= nameNode.Pos() && end >= nameNode.End() { + return access + } + accessNode := access.AsNode() + if accessNode != nil && pos <= accessNode.Pos() && end >= accessNode.End() { + return access + } + } + return nil +} + +func isDynamicImportResultIdentifier(symbol *ast.Symbol) bool { + if symbol == nil { + return false + } + for _, declaration := range symbol.Declarations { + if declaration == nil || declaration.Kind != ast.KindVariableDeclaration { + continue + } + variableDeclaration := declaration.AsVariableDeclaration() + if variableDeclaration == nil || variableDeclaration.Initializer == nil { + continue + } + initializer := ast.SkipParentheses(variableDeclaration.Initializer) + if initializer == nil { + continue + } + if initializer.Kind == ast.KindAwaitExpression { + awaitExpression := initializer.AsAwaitExpression() + if awaitExpression == nil { + continue + } + initializer = ast.SkipParentheses(awaitExpression.Expression) + } + if initializer == nil || initializer.Kind != ast.KindCallExpression { + continue + } + callExpression := initializer.AsCallExpression() + if callExpression == nil || callExpression.Expression == nil { + continue + } + callee := ast.SkipParentheses(callExpression.Expression) + if callee != nil && callee.Kind == ast.KindImportKeyword { + return true + } + } + return false +} + +func isDynamicImportDefaultAccess(node *ast.Node, typeChecker *checker.Checker) bool { + if node == nil || typeChecker == nil { + return false + } + for current := node; current != nil; current = current.Parent { + if current.Kind != ast.KindPropertyAccessExpression { + continue + } + access := current.AsPropertyAccessExpression() + if access == nil || access.Name() == nil || access.Expression == nil { + continue + } + if access.Name().Text() != "default" { + continue + } + target := ast.SkipParentheses(access.Expression) + if target == nil || target.Kind != ast.KindIdentifier { + continue + } + if !isDynamicImportResultIdentifier(typeChecker.GetSymbolAtLocation(target)) { + continue + } + // Ignore only direct default access on the import result. + parent := access.AsNode().Parent + if parent != nil && parent.Kind == ast.KindPropertyAccessExpression { + parentAccess := parent.AsPropertyAccessExpression() + if parentAccess != nil && parentAccess.Expression == access.AsNode() && parentAccess.Name() != nil && parentAccess.Name().Text() == "default" { + return false + } + } + return true + } + return false +} + +func shouldIgnoreDynamicImportDefault(node *ast.Node, pos int, end int, entityName string, typeChecker *checker.Checker) bool { + if entityName != "default" || node == nil || typeChecker == nil { + return false + } + access := propertyAccessForDiagnosticRange(node, pos, end) + if access == nil || access.Name() == nil || access.Expression == nil { + return false + } + if access.Name().Text() != "default" { + return false + } + target := ast.SkipParentheses(access.Expression) + if target == nil || target.Kind != ast.KindIdentifier { + return false + } + if isDynamicImportResultIdentifier(typeChecker.GetSymbolAtLocation(target)) { + return true + } + return isDynamicImportResultIdentifier(typeChecker.GetSymbolAtLocation(access.Name())) +} + +func promotedDynamicImportDefaultRange(node *ast.Node, pos int, end int, entityName string, typeChecker *checker.Checker) *core.TextRange { + if entityName != "default" || node == nil || typeChecker == nil { + return nil + } + access := propertyAccessForDiagnosticRange(node, pos, end) + if access == nil || access.Expression == nil { + return nil + } + target := ast.SkipParentheses(access.Expression) + if target == nil || target.Kind != ast.KindIdentifier { + return nil + } + if !isDynamicImportResultIdentifier(typeChecker.GetSymbolAtLocation(target)) { + return nil + } + if access.AsNode().Parent == nil || access.AsNode().Parent.Kind != ast.KindPropertyAccessExpression { + return nil + } + parentAccess := access.AsNode().Parent.AsPropertyAccessExpression() + if parentAccess == nil || parentAccess.Expression != access.AsNode() || parentAccess.Name() == nil { + return nil + } + if parentAccess.Name().Text() != "default" { + return nil + } + promoted := core.NewTextRange(parentAccess.Name().Pos(), parentAccess.Name().End()) + return &promoted +} + +func isWithinJsxClosingElement(node *ast.Node, pos int, end int) bool { + for current := node; current != nil; current = current.Parent { + if current.Kind != ast.KindJsxClosingElement { + continue + } + closingElement := current.AsJsxClosingElement() + if closingElement == nil || closingElement.TagName == nil { + continue + } + if closingElement.TagName.Pos() == pos && closingElement.TagName.End() == end { + return true + } + } + return false +} + +func isImportBindingAtRange(node *ast.Node, pos int, end int) bool { + for current := node; current != nil; current = current.Parent { + switch current.Kind { + case ast.KindImportSpecifier: + specifier := current.AsImportSpecifier() + if specifier != nil && specifier.Name() != nil { + nameNode := specifier.Name() + if nameNode.Pos() == pos && nameNode.End() == end { + return true + } + } + case ast.KindImportClause: + clause := current.AsImportClause() + if clause != nil && clause.Name() != nil { + nameNode := clause.Name() + if nameNode.Pos() == pos && nameNode.End() == end { + return true + } + } + case ast.KindNamespaceImport: + namespaceImport := current.AsNamespaceImport() + if namespaceImport != nil && namespaceImport.Name() != nil { + nameNode := namespaceImport.Name() + if nameNode.Pos() == pos && nameNode.End() == end { + return true + } + } + case ast.KindImportEqualsDeclaration: + importEquals := current.AsImportEqualsDeclaration() + if importEquals != nil && importEquals.Name() != nil { + nameNode := importEquals.Name() + if nameNode.Pos() == pos && nameNode.End() == end { + return true + } + } + } + } + return false +} + +func isInImportStatementRange(sourceFile *ast.SourceFile, pos int) bool { + if sourceFile == nil { + return false + } + text := sourceFile.Text() + if pos < 0 || pos >= len(text) { + return false + } + lineStart := strings.LastIndex(text[:pos], "\n") + 1 + lineEndRelative := strings.Index(text[pos:], "\n") + lineEnd := len(text) + if lineEndRelative >= 0 { + lineEnd = pos + lineEndRelative + } + lineText := text[lineStart:lineEnd] + trimmedLine := strings.TrimSpace(lineText) + if !strings.HasPrefix(trimmedLine, "import ") { + return false + } + if fromIndex := strings.Index(lineText, " from "); fromIndex >= 0 { + return pos < lineStart+fromIndex + } + return true +} + +func symbolIsDeprecated(typeChecker *checker.Checker, symbol *ast.Symbol) bool { + if typeChecker == nil || symbol == nil { + return false + } + if symbol.ValueDeclaration != nil && (typeChecker.IsDeprecatedDeclaration(symbol.ValueDeclaration) || hasDeprecatedTag(symbol.ValueDeclaration) || hasDeprecatedTagInSource(symbol.ValueDeclaration)) { + return true + } + if len(symbol.Declarations) == 0 { + return false + } + for _, declaration := range symbol.Declarations { + if declaration == nil || (!typeChecker.IsDeprecatedDeclaration(declaration) && !hasDeprecatedTag(declaration) && !hasDeprecatedTagInSource(declaration)) { + return false + } + } + return true +} + +func bindingElementPropertyName(bindingElement *ast.BindingElement) string { + if bindingElement == nil { + return "" + } + if bindingElement.PropertyName != nil { + switch bindingElement.PropertyName.Kind { + case ast.KindIdentifier: + return bindingElement.PropertyName.AsIdentifier().Text + case ast.KindStringLiteral: + return bindingElement.PropertyName.AsStringLiteral().Text + case ast.KindNumericLiteral: + return bindingElement.PropertyName.Text() + } + } + if bindingElement.Name() != nil && bindingElement.Name().Kind == ast.KindIdentifier { + return bindingElement.Name().AsIdentifier().Text + } + return "" +} + +func bindingElementIndex(bindingElement *ast.BindingElement) (int, bool) { + if bindingElement == nil || bindingElement.Parent == nil || bindingElement.Parent.Kind != ast.KindArrayBindingPattern { + return 0, false + } + pattern := bindingElement.Parent.AsBindingPattern() + if pattern == nil || pattern.Elements == nil { + return 0, false + } + for i, element := range pattern.Elements.Nodes { + if element == nil { + continue + } + if element == bindingElement.AsNode() { + return i, true + } + } + return 0, false +} + +func resolveConstantPropertyName(ctx rule.RuleContext, node *ast.Node, depth int, seen map[*ast.Symbol]bool) (string, bool) { + if ctx.TypeChecker == nil || node == nil || depth > maxConstantPropertyResolveDepth { + return "", false + } + node = ast.SkipParentheses(node) + if node == nil { + return "", false + } + switch node.Kind { + case ast.KindStringLiteral: + stringLiteral := node.AsStringLiteral() + if stringLiteral == nil { + return "", false + } + return stringLiteral.Text, true + case ast.KindNoSubstitutionTemplateLiteral: + templateLiteral := node.AsNoSubstitutionTemplateLiteral() + if templateLiteral == nil { + return "", false + } + return templateLiteral.Text, true + case ast.KindNumericLiteral: + numericLiteral := node.AsNumericLiteral() + if numericLiteral == nil { + return "", false + } + return numericLiteral.Text, true + case ast.KindAsExpression: + asExpression := node.AsAsExpression() + if asExpression == nil { + return "", false + } + return resolveConstantPropertyName(ctx, asExpression.Expression, depth+1, seen) + case ast.KindTypeAssertionExpression: + typeAssertion := node.AsTypeAssertion() + if typeAssertion == nil { + return "", false + } + return resolveConstantPropertyName(ctx, typeAssertion.Expression, depth+1, seen) + case ast.KindPropertyAccessExpression: + propertyAccess := node.AsPropertyAccessExpression() + if propertyAccess == nil || propertyAccess.Name() == nil { + return "", false + } + symbol := ctx.TypeChecker.GetSymbolAtLocation(propertyAccess.Name()) + if symbol != nil && symbol.ValueDeclaration != nil && symbol.ValueDeclaration.Kind == ast.KindEnumMember { + enumMember := symbol.ValueDeclaration.AsEnumMember() + if enumMember != nil { + return resolveConstantPropertyName(ctx, enumMember.Initializer, depth+1, seen) + } + } + case ast.KindIdentifier: + symbol := ctx.TypeChecker.GetSymbolAtLocation(node) + if symbol == nil || symbol.ValueDeclaration == nil || symbol.ValueDeclaration.Kind != ast.KindVariableDeclaration { + return "", false + } + if seen[symbol] { + return "", false + } + seen[symbol] = true + defer delete(seen, symbol) + + variableDeclaration := symbol.ValueDeclaration.AsVariableDeclaration() + if variableDeclaration == nil || variableDeclaration.Initializer == nil { + return "", false + } + return resolveConstantPropertyName(ctx, variableDeclaration.Initializer, depth+1, seen) + } + if constantValue := ctx.TypeChecker.GetConstantValue(node); constantValue != nil { + if text, ok := constantValue.(string); ok { + return text, true + } + switch value := constantValue.(type) { + case float64: + return strconv.FormatFloat(value, 'f', -1, 64), true + case int: + return strconv.Itoa(value), true + case int32: + return strconv.Itoa(int(value)), true + case int64: + return strconv.FormatInt(value, 10), true + } + } + return "", false +} + +func elementAccessPropertyName(ctx rule.RuleContext, argument *ast.Node) (string, bool) { + return resolveConstantPropertyName(ctx, argument, 0, map[*ast.Symbol]bool{}) +} + +func isPropertyLikeDeclaration(node *ast.Node) bool { + if node == nil { + return false + } + switch node.Kind { + case ast.KindPropertyDeclaration, + ast.KindPropertySignature, + ast.KindMethodDeclaration, + ast.KindMethodSignature, + ast.KindGetAccessor, + ast.KindSetAccessor: + return true + default: + return false + } +} + +func declarationNameNode(node *ast.Node) *ast.Node { + if node == nil { + return nil + } + if node.Kind == ast.KindVariableDeclaration { + variableDeclaration := node.AsVariableDeclaration() + if variableDeclaration == nil { + return nil + } + return variableDeclaration.Name() + } + return node.Name() +} + +func deprecatedInfoByNameInSource(ctx rule.RuleContext, name string, propertyOnly bool) (bool, string) { + if ctx.TypeChecker == nil || ctx.SourceFile == nil || name == "" { + return false, "" + } + targetName := normalizeComparableName(name) + if targetName == "" { + return false, "" + } + found := false + reason := "" + walkAst(ctx.SourceFile.AsNode(), func(node *ast.Node) bool { + if node == nil { + return false + } + if propertyOnly && !isPropertyLikeDeclaration(node) { + return false + } + nameNode := declarationNameNode(node) + if nameNode == nil || normalizeComparableName(nodeNameText(nameNode)) != targetName { + return false + } + if !ctx.TypeChecker.IsDeprecatedDeclaration(node) && !hasDeprecatedTag(node) && !hasDeprecatedTagInSource(node) { + return false + } + found = true + if reason == "" { + reason = deprecatedReasonFromDeclaration(node) + } + return reason != "" + }) + return found, reason +} + +func deprecatedReasonByNameInSource(ctx rule.RuleContext, name string) string { + _, reason := deprecatedInfoByNameInSource(ctx, name, false) + return reason +} + +func deprecatedPropertyInfoByNameInSource(ctx rule.RuleContext, name string) (bool, string) { + return deprecatedInfoByNameInSource(ctx, name, true) +} + +var NoDeprecatedRule = rule.CreateRule(rule.Rule{ + Name: "no-deprecated", + Run: func(ctx rule.RuleContext, options any) rule.RuleListeners { + if ctx.TypeChecker == nil || ctx.SourceFile == nil { + return rule.RuleListeners{} + } + allowEntries := parseAllowEntries(options) + sourceFile := ctx.SourceFile + if sourceFile.AsNode().Flags&ast.NodeFlagsAmbient != 0 { + // Avoid crashing TypeScript's suggestion diagnostics on ambient source files. + sourceFile = nil + } + // Determine if an identifier is part of a declaration (not a usage). + isDeclaration := func(node *ast.Node) bool { + parent := node.Parent + if parent == nil { + return false + } + switch parent.Kind { + case ast.KindBindingElement: + bindingElement := parent.AsBindingElement() + if bindingElement == nil { + return false + } + if bindingElement.PropertyName != nil && bindingElement.PropertyName == node { + return false + } + if bindingElement.Name() == node { + return bindingElement.PropertyName != nil + } + return false + case ast.KindClassExpression: + fallthrough + case ast.KindVariableDeclaration: + fallthrough + case ast.KindEnumMember: + fallthrough + case ast.KindClassDeclaration: + return parent.Name() == node + case ast.KindMethodDeclaration: + fallthrough + case ast.KindPropertyDeclaration: + fallthrough + case ast.KindGetAccessor: + fallthrough + case ast.KindSetAccessor: + fallthrough + case ast.KindFunctionDeclaration: + fallthrough + case ast.KindInterfaceDeclaration: + fallthrough + case ast.KindTypeAliasDeclaration: + return parent.Name() == node + case ast.KindPropertyAssignment: + propAssign := parent.AsPropertyAssignment() + if propAssign == nil { + return false + } + if propAssign.Initializer == node { + return false + } + return parent.Parent != nil && parent.Parent.Kind == ast.KindObjectLiteralExpression + case ast.KindArrowFunction: + fallthrough + case ast.KindFunctionExpression: + fallthrough + case ast.KindEnumDeclaration: + fallthrough + case ast.KindModuleDeclaration: + fallthrough + case ast.KindMethodSignature: + fallthrough + case ast.KindPropertySignature: + fallthrough + case ast.KindTypeParameter: + fallthrough + case ast.KindParameter: + return true + case ast.KindImportEqualsDeclaration: + return parent.Name() == node + default: + return false + } + } + isInsideImport := func(node *ast.Node) bool { + for current := node; current != nil; current = current.Parent { + kind := current.Kind + if kind == ast.KindImportDeclaration { + return true + } + if kind == ast.KindSourceFile || + kind == ast.KindBlock || + kind == ast.KindFunctionDeclaration || + kind == ast.KindFunctionExpression || + kind == ast.KindArrowFunction || + kind == ast.KindClassDeclaration || + kind == ast.KindClassExpression { + return false + } + } + return false + } + reported := map[string]bool{} + reportRange := func(diagnosticRange core.TextRange, message rule.RuleMessage) { + key := strconv.Itoa(diagnosticRange.Pos()) + ":" + strconv.Itoa(diagnosticRange.End()) + ":" + message.Id + if reported[key] { + return + } + reported[key] = true + ctx.ReportRange(diagnosticRange, message) + } + if sourceFile != nil { + diagnostics := ctx.TypeChecker.GetSuggestionDiagnostics(context.Background(), sourceFile) + for _, diagnostic := range diagnostics { + if diagnostic == nil || !diagnostic.ReportsDeprecated() || diagnostic.File() != sourceFile { + continue + } + name := diagnosticEntityName(diagnostic) + node := diagnosticNode(sourceFile, diagnostic.Pos(), diagnostic.End()) + if shouldIgnoreDynamicImportDefault(node, diagnostic.Pos(), diagnostic.End(), name, ctx.TypeChecker) { + continue + } + symbol := symbolAtLocation(ctx.TypeChecker, node) + if isWithinJsxClosingElement(node, diagnostic.Pos(), diagnostic.End()) { + continue + } + if isImportBindingAtRange(node, diagnostic.Pos(), diagnostic.End()) { + continue + } + if isInImportStatementRange(sourceFile, diagnostic.Pos()) { + continue + } + diagnosticRange := core.NewTextRange(diagnostic.Pos(), diagnostic.End()) + if promotedRange := promotedDynamicImportDefaultRange(node, diagnostic.Pos(), diagnostic.End(), name, ctx.TypeChecker); promotedRange != nil { + diagnosticRange = *promotedRange + } + if shouldAllowDiagnostic(allowEntries, name, symbol, ctx.SourceFile) { + continue + } + message := buildDeprecatedMessage(name) + if reason := deprecatedReasonFromDiagnostic(diagnostic); reason != "" { + message = buildDeprecatedWithReasonMessage(name, reason) + } else if symbol != nil { + for _, declaration := range symbol.Declarations { + if declaration == nil { + continue + } + if reason := deprecatedReasonFromDeclaration(declaration); reason != "" { + message = buildDeprecatedWithReasonMessage(name, reason) + break + } + } + } + if message.Id != "deprecatedWithReason" { + if reason := deprecatedReasonByNameInSource(ctx, name); reason != "" { + message = buildDeprecatedWithReasonMessage(name, reason) + } + } + reportRange(diagnosticRange, message) + } + } + checkIdentifier := func(node *ast.Node) { + if node == nil { + return + } + if isDeclaration(node) || isInsideImport(node) { + return + } + isDeprecated, reason := getDeprecationReason(ctx, node) + if !isDeprecated { + return + } + symbol := symbolAtLocation(ctx.TypeChecker, node) + if symbol == nil { + symbol = ctx.TypeChecker.GetSymbolAtLocation(node) + } + if symbol != nil && symbol.ValueDeclaration != nil && symbol.ValueDeclaration.Kind == ast.KindBindingElement { + symbol = nil + } + // Only report if the deprecated symbol isn't just a local declaration in the current file. + // Allow reporting for deprecated locals (for example, symbol usage). + name := getReportedNodeName(node) + if shouldAllowDiagnostic(allowEntries, name, symbol, ctx.SourceFile) { + return + } + message := buildDeprecatedMessage(name) + if reason != "" { + message = buildDeprecatedWithReasonMessage(name, reason) + } else if symbol != nil { + for _, declaration := range symbol.Declarations { + if declaration == nil { + continue + } + if reasonFromDecl := deprecatedReasonFromDeclaration(declaration); reasonFromDecl != "" { + message = buildDeprecatedWithReasonMessage(name, reasonFromDecl) + break + } + } + } + if message.Id != "deprecatedWithReason" { + if reasonByName := deprecatedReasonByNameInSource(ctx, name); reasonByName != "" { + message = buildDeprecatedWithReasonMessage(name, reasonByName) + } + } + trimmedRange := utils.TrimNodeTextRange(ctx.SourceFile, node) + reportRange(core.NewTextRange(trimmedRange.Pos(), trimmedRange.End()), message) + } + return rule.RuleListeners{ + ast.KindIdentifier: func(node *ast.Node) { + identifier := node.AsIdentifier() + if identifier == nil { + return + } + if node.Parent == nil { + return + } + // Avoid duplicate reports on declarations and import/export boilerplate. + if isWithinJsxClosingElement(node, node.Pos(), node.End()) { + return + } + if node.Parent.Kind == ast.KindExportDeclaration || node.Parent.Kind == ast.KindNamespaceExport { + return + } + if node.Parent.Kind == ast.KindExportSpecifier { + exportSpec := node.Parent.AsExportSpecifier() + if exportSpec != nil { + isPropertyName := exportSpec.PropertyName != nil && exportSpec.PropertyName.AsNode() == node + if isPropertyName { + return + } + jsdocs := node.Parent.JSDoc(nil) + for _, jsdoc := range jsdocs { + tags := jsdoc.AsJSDoc().Tags + if tags == nil { + continue + } + for _, tagNode := range tags.Nodes { + if ast.IsJSDocDeprecatedTag(tagNode) { + return + } + } + } + } + } + checkIdentifier(node) + }, + ast.KindPrivateIdentifier: checkIdentifier, + ast.KindSuperKeyword: checkIdentifier, + ast.KindJsxAttribute: func(node *ast.Node) { + jsxAttribute := node.AsJsxAttribute() + if jsxAttribute == nil || jsxAttribute.Name() == nil { + return + } + nameNode := jsxAttribute.Name() + nameText := nameNode.Text() + if nameText == "" { + return + } + propertySymbol := symbolAtLocation(ctx.TypeChecker, nameNode) + if propertySymbol == nil && node.Parent != nil && node.Parent.Kind == ast.KindJsxAttributes { + attributesType := ctx.TypeChecker.GetTypeAtLocation(node.Parent) + if attributesType != nil { + propertySymbol = checker.Checker_getPropertyOfType(ctx.TypeChecker, attributesType, nameText) + } + } + isDeprecated := symbolIsDeprecated(ctx.TypeChecker, propertySymbol) + sourceDeprecated, sourceReason := deprecatedPropertyInfoByNameInSource(ctx, nameText) + if !isDeprecated && !sourceDeprecated { + return + } + if shouldAllowDiagnostic(allowEntries, nameText, propertySymbol, ctx.SourceFile) { + return + } + message := buildDeprecatedMessage(nameText) + if propertySymbol != nil { + for _, declaration := range propertySymbol.Declarations { + if reason := deprecatedReasonFromDeclaration(declaration); reason != "" { + message = buildDeprecatedWithReasonMessage(nameText, reason) + break + } + } + } + if message.Id != "deprecatedWithReason" && sourceReason != "" { + message = buildDeprecatedWithReasonMessage(nameText, sourceReason) + } + trimmedRange := utils.TrimNodeTextRange(ctx.SourceFile, nameNode) + reportRange(core.NewTextRange(trimmedRange.Pos(), trimmedRange.End()), message) + }, + ast.KindElementAccessExpression: func(node *ast.Node) { + elementAccess := node.AsElementAccessExpression() + if elementAccess == nil || elementAccess.Expression == nil || elementAccess.ArgumentExpression == nil { + return + } + propertyName, ok := elementAccessPropertyName(ctx, elementAccess.ArgumentExpression) + if !ok || propertyName == "" { + return + } + objectType := utils.GetConstrainedTypeAtLocation(ctx.TypeChecker, elementAccess.Expression) + if objectType == nil { + return + } + propertySymbol := checker.Checker_getPropertyOfType(ctx.TypeChecker, objectType, propertyName) + if !symbolIsDeprecated(ctx.TypeChecker, propertySymbol) { + return + } + if shouldAllowDiagnostic(allowEntries, propertyName, propertySymbol, ctx.SourceFile) { + return + } + message := buildDeprecatedMessage(propertyName) + for _, declaration := range propertySymbol.Declarations { + if reason := deprecatedReasonFromDeclaration(declaration); reason != "" { + message = buildDeprecatedWithReasonMessage(propertyName, reason) + break + } + } + trimmedRange := utils.TrimNodeTextRange(ctx.SourceFile, elementAccess.ArgumentExpression) + reportRange(core.NewTextRange(trimmedRange.Pos(), trimmedRange.End()), message) + }, + ast.KindPropertyAccessExpression: func(node *ast.Node) { + access := node.AsPropertyAccessExpression() + if access == nil || access.Name() == nil { + return + } + // Report the property name if it is deprecated. + nameNode := access.Name() + propertySymbol := ctx.TypeChecker.GetSymbolAtLocation(nameNode) + if !symbolIsDeprecated(ctx.TypeChecker, propertySymbol) { + return + } + name := nameNode.Text() + if shouldAllowDiagnostic(allowEntries, name, propertySymbol, ctx.SourceFile) { + return + } + message := buildDeprecatedMessage(name) + for _, declaration := range propertySymbol.Declarations { + if reason := deprecatedReasonFromDeclaration(declaration); reason != "" { + message = buildDeprecatedWithReasonMessage(name, reason) + break + } + } + trimmedRange := utils.TrimNodeTextRange(ctx.SourceFile, nameNode) + reportRange(core.NewTextRange(trimmedRange.Pos(), trimmedRange.End()), message) + }, + } + }, +}) diff --git a/internal/plugins/typescript/rules/no_deprecated/no_deprecated.md b/internal/plugins/typescript/rules/no_deprecated/no_deprecated.md new file mode 100644 index 000000000..1be5a69da --- /dev/null +++ b/internal/plugins/typescript/rules/no_deprecated/no_deprecated.md @@ -0,0 +1,9 @@ +# no-deprecated + +## Rule Details + +Disallow usage of declarations marked with `@deprecated`. + +## Original Documentation + +- https://typescript-eslint.io/rules/no-deprecated diff --git a/internal/plugins/typescript/rules/no_deprecated/no_deprecated_test.go b/internal/plugins/typescript/rules/no_deprecated/no_deprecated_test.go new file mode 100644 index 000000000..d84e48076 --- /dev/null +++ b/internal/plugins/typescript/rules/no_deprecated/no_deprecated_test.go @@ -0,0 +1,141 @@ +package no_deprecated + +import ( + "testing" + + "github.com/microsoft/typescript-go/shim/ast" + "github.com/microsoft/typescript-go/shim/tspath" + "github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/fixtures" + "github.com/web-infra-dev/rslint/internal/rule_tester" + "github.com/web-infra-dev/rslint/internal/utils" +) + +func sourceFileFromCode(t *testing.T, code string) *ast.SourceFile { + t.Helper() + rootDir := fixtures.GetRootDir() + fileName := "file.ts" + fs := utils.NewOverlayVFSForFile(tspath.ResolvePath(rootDir, fileName), code) + host := utils.CreateCompilerHost(rootDir, fs) + program, err := utils.CreateProgram(true, fs, rootDir, "tsconfig.json", host) + if err != nil { + t.Fatalf("failed to create program: %v", err) + } + sourceFile := program.GetSourceFile(fileName) + if sourceFile == nil { + t.Fatalf("failed to resolve source file for %s", fileName) + } + return sourceFile +} + +func TestNameImportedFromPackage(t *testing.T) { + sourceFile := sourceFileFromCode(t, ` +import { oldValue } from 'pkg'; +const another = 1; +`) + if !nameImportedFromPackage(sourceFile, "oldValue", "pkg") { + t.Fatalf("expected static import to match package allow entry") + } + if nameImportedFromPackage(sourceFile, "oldValue", "other-pkg") { + t.Fatalf("did not expect import to match other package") + } +} + +func TestNameImportedFromPackageDynamicImport(t *testing.T) { + sourceFile := sourceFileFromCode(t, ` +async function run() { + const { oldValue } = await import('pkg'); + oldValue(); +} +`) + if !nameImportedFromPackage(sourceFile, "oldValue", "pkg") { + t.Fatalf("expected dynamic import binding to match package allow entry") + } +} + +func TestNoDeprecatedRule(t *testing.T) { + rule_tester.RunRuleTester(fixtures.GetRootDir(), "tsconfig.json", t, &NoDeprecatedRule, []rule_tester.ValidTestCase{ + {Code: `const value = 1; value;`}, + { + Code: ` +/** @deprecated */ +const oldValue = 1; +oldValue; + `, + Options: []interface{}{ + map[string]interface{}{ + "allow": []interface{}{"oldValue"}, + }, + }, + }, + { + Code: ` +/** @deprecated */ +const oldValue = 1; +oldValue; + `, + Options: []interface{}{ + map[string]interface{}{ + "allow": []interface{}{ + map[string]interface{}{ + "from": "file", + "name": "oldValue", + }, + }, + }, + }, + }, + { + Code: ` +/** @deprecated */ +const oldValue = 1; +oldValue; + `, + Options: map[string]interface{}{ + "allow": []interface{}{ + map[string]interface{}{ + "from": "file", + "name": "oldValue", + }, + }, + }, + }, + }, []rule_tester.InvalidTestCase{ + { + Code: `/** @deprecated */ const oldValue = 1; oldValue;`, + Errors: []rule_tester.InvalidTestCaseError{ + {MessageId: "deprecated", Line: 1, Column: 40}, + }, + }, + { + Code: ` +/** @deprecated Use newValue instead. */ +const oldValue = 1; +oldValue; + `, + Errors: []rule_tester.InvalidTestCaseError{ + {MessageId: "deprecatedWithReason"}, + }, + }, + { + Code: ` +/** @deprecated */ +const oldValue = 1; +oldValue; + `, + Options: []interface{}{ + map[string]interface{}{ + "allow": []interface{}{ + map[string]interface{}{ + "from": "package", + "name": "oldValue", + "package": "other-pkg", + }, + }, + }, + }, + Errors: []rule_tester.InvalidTestCaseError{ + {MessageId: "deprecated"}, + }, + }, + }) +} diff --git a/packages/rslint-test-tools/rstest.config.mts b/packages/rslint-test-tools/rstest.config.mts index c1b8a03d5..f7e5e6d8e 100644 --- a/packages/rslint-test-tools/rstest.config.mts +++ b/packages/rslint-test-tools/rstest.config.mts @@ -66,7 +66,7 @@ export default defineConfig({ // './tests/typescript-eslint/rules/no-base-to-string.test.ts', // './tests/typescript-eslint/rules/no-confusing-non-null-assertion.test.ts', // './tests/typescript-eslint/rules/no-confusing-void-expression.test.ts', - // './tests/typescript-eslint/rules/no-deprecated.test.ts', + './tests/typescript-eslint/rules/no-deprecated.test.ts', // './tests/typescript-eslint/rules/no-dupe-class-members.test.ts', './tests/typescript-eslint/rules/no-duplicate-enum-values.test.ts', './tests/typescript-eslint/rules/no-duplicate-type-constituents.test.ts', diff --git a/packages/rslint-test-tools/tests/typescript-eslint/rules/__snapshots__/no-deprecated.test.ts.snap b/packages/rslint-test-tools/tests/typescript-eslint/rules/__snapshots__/no-deprecated.test.ts.snap new file mode 100644 index 000000000..13b57022b --- /dev/null +++ b/packages/rslint-test-tools/tests/typescript-eslint/rules/__snapshots__/no-deprecated.test.ts.snap @@ -0,0 +1,2365 @@ +// Rstest Snapshot v1 + +exports[`no-deprecated > invalid 1`] = ` +{ + "code": " + /** @deprecated */ var a = undefined; + a; + ", + "diagnostics": [ + { + "message": "\`a\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 10, + "line": 3, + }, + "start": { + "column": 9, + "line": 3, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 2`] = ` +{ + "code": " + /** @deprecated */ export var a = undefined; + a; + ", + "diagnostics": [ + { + "message": "\`a\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 10, + "line": 3, + }, + "start": { + "column": 9, + "line": 3, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 3`] = ` +{ + "code": " + /** @deprecated */ let a = undefined; + a; + ", + "diagnostics": [ + { + "message": "\`a\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 10, + "line": 3, + }, + "start": { + "column": 9, + "line": 3, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 4`] = ` +{ + "code": " + /** @deprecated */ export let a = undefined; + a; + ", + "diagnostics": [ + { + "message": "\`a\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 10, + "line": 3, + }, + "start": { + "column": 9, + "line": 3, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 5`] = ` +{ + "code": " + /** @deprecated */ let aLongName = undefined; + aLongName; + ", + "diagnostics": [ + { + "message": "\`aLongName\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 18, + "line": 3, + }, + "start": { + "column": 9, + "line": 3, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 6`] = ` +{ + "code": " + /** @deprecated */ const a = { b: 1 }; + const c = a; + ", + "diagnostics": [ + { + "message": "\`a\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 20, + "line": 3, + }, + "start": { + "column": 19, + "line": 3, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 7`] = ` +{ + "code": " + /** @deprecated Reason. */ const a = { b: 1 }; + const c = a; + ", + "diagnostics": [ + { + "message": "\`a\` is deprecated. Reason.", + "messageId": "deprecatedWithReason", + "range": { + "end": { + "column": 20, + "line": 3, + }, + "start": { + "column": 19, + "line": 3, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 8`] = ` +{ + "code": " + /** @deprecated */ const a = { b: 1 }; + const { c = a } = {}; + ", + "diagnostics": [ + { + "message": "\`a\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 22, + "line": 3, + }, + "start": { + "column": 21, + "line": 3, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 9`] = ` +{ + "code": " + /** @deprecated */ const a = { b: 1 }; + const [c = a] = []; + ", + "diagnostics": [ + { + "message": "\`a\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 21, + "line": 3, + }, + "start": { + "column": 20, + "line": 3, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 10`] = ` +{ + "code": " + /** @deprecated */ const a = { b: 1 }; + console.log(a); + ", + "diagnostics": [ + { + "message": "\`a\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 22, + "line": 3, + }, + "start": { + "column": 21, + "line": 3, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 11`] = ` +{ + "code": " + /** @deprecated */ const a = 'foo'; + import(\`./path/\${a}.js\`); + ", + "diagnostics": [ + { + "message": "\`a\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 27, + "line": 3, + }, + "start": { + "column": 26, + "line": 3, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 12`] = ` +{ + "code": " + declare function log(...args: unknown): void; + + /** @deprecated */ const a = { b: 1 }; + + log(a); + ", + "diagnostics": [ + { + "message": "\`a\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 14, + "line": 6, + }, + "start": { + "column": 13, + "line": 6, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 13`] = ` +{ + "code": " + /** @deprecated */ const a = { b: 1 }; + console.log(a.b); + ", + "diagnostics": [ + { + "message": "\`a\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 22, + "line": 3, + }, + "start": { + "column": 21, + "line": 3, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 14`] = ` +{ + "code": " + /** @deprecated */ const a = { b: 1 }; + console.log(a?.b); + ", + "diagnostics": [ + { + "message": "\`a\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 22, + "line": 3, + }, + "start": { + "column": 21, + "line": 3, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 15`] = ` +{ + "code": " + /** @deprecated */ const a = { b: { c: 1 } }; + a.b.c; + ", + "diagnostics": [ + { + "message": "\`a\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 10, + "line": 3, + }, + "start": { + "column": 9, + "line": 3, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 16`] = ` +{ + "code": " + /** @deprecated */ const a = { b: { c: 1 } }; + a.b?.c; + ", + "diagnostics": [ + { + "message": "\`a\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 10, + "line": 3, + }, + "start": { + "column": 9, + "line": 3, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 17`] = ` +{ + "code": " + /** @deprecated */ const a = { b: { c: 1 } }; + a?.b?.c; + ", + "diagnostics": [ + { + "message": "\`a\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 10, + "line": 3, + }, + "start": { + "column": 9, + "line": 3, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 18`] = ` +{ + "code": " + const a = { + /** @deprecated */ b: { c: 1 }, + }; + a.b.c; + ", + "diagnostics": [ + { + "message": "\`b\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 12, + "line": 5, + }, + "start": { + "column": 11, + "line": 5, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 19`] = ` +{ + "code": " + declare const a: { + /** @deprecated */ b: { c: 1 }; + }; + a.b.c; + ", + "diagnostics": [ + { + "message": "\`b\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 12, + "line": 5, + }, + "start": { + "column": 11, + "line": 5, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 20`] = ` +{ + "code": " + /** @deprecated */ const a = { b: 1 }; + const c = a.b; + ", + "diagnostics": [ + { + "message": "\`a\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 20, + "line": 3, + }, + "start": { + "column": 19, + "line": 3, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 21`] = ` +{ + "code": " + /** @deprecated */ const a = { b: 1 }; + const { c } = a.b; + ", + "diagnostics": [ + { + "message": "\`a\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 24, + "line": 3, + }, + "start": { + "column": 23, + "line": 3, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 22`] = ` +{ + "code": " + /** @deprecated */ + declare const test: string; + const myObj = { + prop: test, + deep: { + prop: test, + }, + }; + ", + "diagnostics": [ + { + "message": "\`test\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 21, + "line": 5, + }, + "start": { + "column": 17, + "line": 5, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + { + "message": "\`test\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 23, + "line": 7, + }, + "start": { + "column": 19, + "line": 7, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 2, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 23`] = ` +{ + "code": " + /** @deprecated */ + declare const test: string; + const bar = { + test, + }; + ", + "diagnostics": [ + { + "message": "\`test\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 15, + "line": 5, + }, + "start": { + "column": 11, + "line": 5, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 24`] = ` +{ + "code": " + /** @deprecated */ const a = { b: 1 }; + const { c = 'd' } = a.b; + ", + "diagnostics": [ + { + "message": "\`a\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 30, + "line": 3, + }, + "start": { + "column": 29, + "line": 3, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 25`] = ` +{ + "code": " + /** @deprecated */ const a = { b: 1 }; + const { c: d } = a.b; + ", + "diagnostics": [ + { + "message": "\`a\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 27, + "line": 3, + }, + "start": { + "column": 26, + "line": 3, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 26`] = ` +{ + "code": " + /** @deprecated */ + declare const a: string[]; + const [b] = [a]; + ", + "diagnostics": [ + { + "message": "\`a\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 23, + "line": 4, + }, + "start": { + "column": 22, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 27`] = ` +{ + "code": " + /** @deprecated */ + class A {} + + new A(); + ", + "diagnostics": [ + { + "message": "\`A\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 14, + "line": 5, + }, + "start": { + "column": 13, + "line": 5, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 28`] = ` +{ + "code": " + /** @deprecated */ + export class A {} + + new A(); + ", + "diagnostics": [ + { + "message": "\`A\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 14, + "line": 5, + }, + "start": { + "column": 13, + "line": 5, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 29`] = ` +{ + "code": " + /** @deprecated */ + const A = class {}; + + new A(); + ", + "diagnostics": [ + { + "message": "\`A\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 14, + "line": 5, + }, + "start": { + "column": 13, + "line": 5, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 30`] = ` +{ + "code": " + /** @deprecated */ + declare class A {} + + new A(); + ", + "diagnostics": [ + { + "message": "\`A\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 14, + "line": 5, + }, + "start": { + "column": 13, + "line": 5, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 31`] = ` +{ + "code": " + const A = class { + /** @deprecated */ + constructor() {} + }; + + new A(); + ", + "diagnostics": [ + { + "message": "\`A\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 14, + "line": 7, + }, + "start": { + "column": 13, + "line": 7, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 32`] = ` +{ + "code": " + const A = class { + /** @deprecated */ + constructor(); + constructor(arg: string); + constructor(arg?: string) {} + }; + + new A(); + ", + "diagnostics": [ + { + "message": "\`A\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 14, + "line": 9, + }, + "start": { + "column": 13, + "line": 9, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 33`] = ` +{ + "code": " + declare const A: { + /** @deprecated */ + new (): string; + }; + + new A(); + ", + "diagnostics": [ + { + "message": "\`A\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 14, + "line": 7, + }, + "start": { + "column": 13, + "line": 7, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 34`] = ` +{ + "code": " + /** @deprecated */ + declare class A { + constructor(); + } + + new A(); + ", + "diagnostics": [ + { + "message": "\`A\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 14, + "line": 7, + }, + "start": { + "column": 13, + "line": 7, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 35`] = ` +{ + "code": " + class A { + /** @deprecated */ + b: string; + } + + declare const a: A; + + const { b } = a; + ", + "diagnostics": [ + { + "message": "\`b\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 18, + "line": 9, + }, + "start": { + "column": 17, + "line": 9, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 36`] = ` +{ + "code": " + declare class A { + /** @deprecated */ + b(): string; + } + + declare const a: A; + + a.b; + ", + "diagnostics": [ + { + "message": "\`b\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 12, + "line": 9, + }, + "start": { + "column": 11, + "line": 9, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 37`] = ` +{ + "code": " + declare class A { + /** @deprecated */ + b(): string; + } + + declare const a: A; + + a.b(); + ", + "diagnostics": [ + { + "message": "\`a.b\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 12, + "line": 9, + }, + "start": { + "column": 11, + "line": 9, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 38`] = ` +{ + "code": " + declare class A { + /** @deprecated */ + b: () => string; + } + + declare const a: A; + + a.b; + ", + "diagnostics": [ + { + "message": "\`b\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 12, + "line": 9, + }, + "start": { + "column": 11, + "line": 9, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 39`] = ` +{ + "code": " + declare class A { + /** @deprecated */ + b: () => string; + } + + declare const a: A; + + a.b(); + ", + "diagnostics": [ + { + "message": "\`b\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 12, + "line": 9, + }, + "start": { + "column": 11, + "line": 9, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 40`] = ` +{ + "code": " + interface A { + /** @deprecated */ + b: () => string; + } + + declare const a: A; + + a.b(); + ", + "diagnostics": [ + { + "message": "\`b\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 12, + "line": 9, + }, + "start": { + "column": 11, + "line": 9, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 41`] = ` +{ + "code": " + class A { + /** @deprecated */ + b(): string { + return ''; + } + } + + declare const a: A; + + a.b(); + ", + "diagnostics": [ + { + "message": "\`a.b\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 12, + "line": 11, + }, + "start": { + "column": 11, + "line": 11, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 42`] = ` +{ + "code": " + declare class A { + /** @deprecated Use b(value). */ + b(): string; + b(value: string): string; + } + + declare const a: A; + + a.b(); + ", + "diagnostics": [ + { + "message": "\`a.b\` is deprecated. Use b(value).", + "messageId": "deprecatedWithReason", + "range": { + "end": { + "column": 12, + "line": 10, + }, + "start": { + "column": 11, + "line": 10, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 43`] = ` +{ + "code": " + declare class A { + /** @deprecated */ + static b: string; + } + + A.b; + ", + "diagnostics": [ + { + "message": "\`b\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 12, + "line": 7, + }, + "start": { + "column": 11, + "line": 7, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 44`] = ` +{ + "code": " + declare const a: { + /** @deprecated */ + b: string; + }; + + a.b; + ", + "diagnostics": [ + { + "message": "\`b\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 12, + "line": 7, + }, + "start": { + "column": 11, + "line": 7, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 45`] = ` +{ + "code": " + interface A { + /** @deprecated */ + b: string; + } + + declare const a: A; + + a.b; + ", + "diagnostics": [ + { + "message": "\`b\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 12, + "line": 9, + }, + "start": { + "column": 11, + "line": 9, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 46`] = ` +{ + "code": " + export interface A { + /** @deprecated */ + b: string; + } + + declare const a: A; + + a.b; + ", + "diagnostics": [ + { + "message": "\`b\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 12, + "line": 9, + }, + "start": { + "column": 11, + "line": 9, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 47`] = ` +{ + "code": " + interface A { + /** @deprecated */ + b: string; + } + + declare const a: A; + + const { b } = a; + ", + "diagnostics": [ + { + "message": "\`b\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 18, + "line": 9, + }, + "start": { + "column": 17, + "line": 9, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 48`] = ` +{ + "code": " + type A = { + /** @deprecated */ + b: string; + }; + + declare const a: A; + + const { b } = a; + ", + "diagnostics": [ + { + "message": "\`b\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 18, + "line": 9, + }, + "start": { + "column": 17, + "line": 9, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 49`] = ` +{ + "code": " + export type A = { + /** @deprecated */ + b: string; + }; + + declare const a: A; + + const { b } = a; + ", + "diagnostics": [ + { + "message": "\`b\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 18, + "line": 9, + }, + "start": { + "column": 17, + "line": 9, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 50`] = ` +{ + "code": " + type A = () => { + /** @deprecated */ + b: string; + }; + + declare const a: A; + + const { b } = a(); + ", + "diagnostics": [ + { + "message": "\`b\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 18, + "line": 9, + }, + "start": { + "column": 17, + "line": 9, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 51`] = ` +{ + "code": " + /** @deprecated */ + type A = string[]; + + declare const a: A; + + const [b] = a; + ", + "diagnostics": [ + { + "message": "\`A\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 27, + "line": 5, + }, + "start": { + "column": 26, + "line": 5, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 52`] = ` +{ + "code": " + namespace A { + /** @deprecated */ + export const b = ''; + } + + A.b; + ", + "diagnostics": [ + { + "message": "\`b\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 12, + "line": 7, + }, + "start": { + "column": 11, + "line": 7, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 53`] = ` +{ + "code": " + export namespace A { + /** @deprecated */ + export const b = ''; + } + + A.b; + ", + "diagnostics": [ + { + "message": "\`b\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 12, + "line": 7, + }, + "start": { + "column": 11, + "line": 7, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 54`] = ` +{ + "code": " + namespace A { + /** @deprecated */ + export function b() {} + } + + A.b(); + ", + "diagnostics": [ + { + "message": "\`A.b\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 12, + "line": 7, + }, + "start": { + "column": 11, + "line": 7, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 55`] = ` +{ + "code": " + namespace assert { + export function fail(message?: string | Error): never; + /** @deprecated since v10.0.0 - use fail([message]) or other assert functions instead. */ + export function fail(actual: unknown, expected: unknown): never; + } + + assert.fail({}, {}); + ", + "diagnostics": [ + { + "message": "\`assert.fail\` is deprecated. since v10.0.0 - use fail([message]) or other assert functions instead.", + "messageId": "deprecatedWithReason", + "range": { + "end": { + "column": 20, + "line": 8, + }, + "start": { + "column": 16, + "line": 8, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 56`] = ` +{ + "code": " + import assert from 'node:assert'; + + assert.fail({}, {}); + ", + "diagnostics": [ + { + "message": "\`assert.fail\` is deprecated. since v10.0.0 - use fail([message]) or other assert functions instead.", + "messageId": "deprecatedWithReason", + "range": { + "end": { + "column": 20, + "line": 4, + }, + "start": { + "column": 16, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 57`] = ` +{ + "code": " + /** @deprecated */ + enum A { + a, + } + + A.a; + ", + "diagnostics": [ + { + "message": "\`A\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 10, + "line": 7, + }, + "start": { + "column": 9, + "line": 7, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 58`] = ` +{ + "code": " + enum A { + /** @deprecated */ + a, + } + + A.a; + ", + "diagnostics": [ + { + "message": "\`a\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 12, + "line": 7, + }, + "start": { + "column": 11, + "line": 7, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 59`] = ` +{ + "code": " + /** @deprecated */ + function a() {} + + a(); + ", + "diagnostics": [ + { + "message": "\`a\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 10, + "line": 5, + }, + "start": { + "column": 9, + "line": 5, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 60`] = ` +{ + "code": " + /** @deprecated */ + function a(): void; + function a() {} + + a(); + ", + "diagnostics": [ + { + "message": "\`a\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 10, + "line": 6, + }, + "start": { + "column": 9, + "line": 6, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 61`] = ` +{ + "code": " + function a(): void; + /** @deprecated */ + function a(value: string): void; + function a(value?: string) {} + + a(''); + ", + "diagnostics": [ + { + "message": "\`a\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 10, + "line": 7, + }, + "start": { + "column": 9, + "line": 7, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 62`] = ` +{ + "code": " + type A = { + (value: 'b'): void; + /** @deprecated */ + (value: 'c'): void; + }; + declare const foo: A; + foo('c'); + ", + "diagnostics": [ + { + "message": "\`foo\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 12, + "line": 8, + }, + "start": { + "column": 9, + "line": 8, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 63`] = ` +{ + "code": " + function a( + /** @deprecated */ + b?: boolean, + ) { + return b; + } + ", + "diagnostics": [ + { + "message": "\`b\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 19, + "line": 6, + }, + "start": { + "column": 18, + "line": 6, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 64`] = ` +{ + "code": " + export function isTypeFlagSet( + type: ts.Type, + flagsToCheck: ts.TypeFlags, + /** @deprecated This param is not used and will be removed in the future. */ + isReceiver?: boolean, + ): boolean { + const flags = getTypeFlags(type); + + if (isReceiver && flags & ANY_OR_UNKNOWN) { + return true; + } + + return (flags & flagsToCheck) !== 0; + } + ", + "diagnostics": [ + { + "message": "\`isReceiver\` is deprecated. This param is not used and will be removed in the future.", + "messageId": "deprecatedWithReason", + "range": { + "end": { + "column": 25, + "line": 10, + }, + "start": { + "column": 15, + "line": 10, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 65`] = ` +{ + "code": " + /** @deprecated */ + declare function a(...args: unknown[]): string; + + a\`\`; + ", + "diagnostics": [ + { + "message": "\`a\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 10, + "line": 5, + }, + "start": { + "column": 9, + "line": 5, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 66`] = ` +{ + "code": " + /** @deprecated */ + const A = () =>
; + + const a = ; + ", + "diagnostics": [ + { + "message": "\`A\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 21, + "line": 5, + }, + "start": { + "column": 20, + "line": 5, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 67`] = ` +{ + "code": " + /** @deprecated */ + const A = () =>
; + + const a = ; + ", + "diagnostics": [ + { + "message": "\`A\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 21, + "line": 5, + }, + "start": { + "column": 20, + "line": 5, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 68`] = ` +{ + "code": " + /** @deprecated */ + function A() { + return
; + } + + const a = ; + ", + "diagnostics": [ + { + "message": "\`A\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 21, + "line": 7, + }, + "start": { + "column": 20, + "line": 7, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 69`] = ` +{ + "code": " + /** @deprecated */ + function A() { + return
; + } + + const a = ; + ", + "diagnostics": [ + { + "message": "\`A\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 21, + "line": 7, + }, + "start": { + "column": 20, + "line": 7, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 70`] = ` +{ + "code": " + /** @deprecated */ + export type A = string; + export type B = string; + export type C = string; + + export type D = A | B | C; + ", + "diagnostics": [ + { + "message": "\`A\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 26, + "line": 7, + }, + "start": { + "column": 25, + "line": 7, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 71`] = ` +{ + "code": " + namespace A { + /** @deprecated */ + export type B = string; + export type C = string; + export type D = string; + } + + export type D = A.B | A.C | A.D; + ", + "diagnostics": [ + { + "message": "\`B\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 28, + "line": 9, + }, + "start": { + "column": 27, + "line": 9, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 72`] = ` +{ + "code": " + interface Props { + /** @deprecated */ + anchor: 'foo'; + } + declare const x: Props; + const { anchor = '' } = x; + ", + "diagnostics": [ + { + "message": "\`anchor\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 23, + "line": 7, + }, + "start": { + "column": 17, + "line": 7, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 73`] = ` +{ + "code": " + interface Props { + /** @deprecated */ + anchor: 'foo'; + } + declare const x: { bar: Props }; + const { + bar: { anchor = '' }, + } = x; + ", + "diagnostics": [ + { + "message": "\`anchor\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 24, + "line": 8, + }, + "start": { + "column": 18, + "line": 8, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; diff --git a/packages/rslint-test-tools/tests/typescript-eslint/rules/no-deprecated.test.ts b/packages/rslint-test-tools/tests/typescript-eslint/rules/no-deprecated.test.ts index 909fd66bf..16cca2723 100644 --- a/packages/rslint-test-tools/tests/typescript-eslint/rules/no-deprecated.test.ts +++ b/packages/rslint-test-tools/tests/typescript-eslint/rules/no-deprecated.test.ts @@ -1,4 +1,4 @@ -import { RuleTester } from '@typescript-eslint/rule-tester'; +import { noFormat, RuleTester } from '@typescript-eslint/rule-tester'; import { getFixturesRootDir } from '../RuleTester'; @@ -15,6 +15,7 @@ const ruleTester = new RuleTester({ }, }, }); +const jsxLanguageOptions = ruleTester.options.languageOptions; ruleTester.run('no-deprecated', { valid: [ @@ -286,51 +287,66 @@ ruleTester.run('no-deprecated', { } } `, - ` - declare namespace JSX {} + { + code: noFormat` + declare namespace JSX {} - ; - `, - ` - declare namespace JSX { - interface IntrinsicElements { - foo: any; + ; + `, + languageOptions: jsxLanguageOptions, + }, + { + code: noFormat` + declare namespace JSX { + interface IntrinsicElements { + foo: any; + } } - } - ; - `, - ` - declare namespace JSX { - interface IntrinsicElements { - foo: unknown; + ; + `, + languageOptions: jsxLanguageOptions, + }, + { + code: noFormat` + declare namespace JSX { + interface IntrinsicElements { + foo: unknown; + } } - } - ; - `, - ` - declare namespace JSX { - interface IntrinsicElements { - foo: { - bar: any; - }; + ; + `, + languageOptions: jsxLanguageOptions, + }, + { + code: noFormat` + declare namespace JSX { + interface IntrinsicElements { + foo: { + bar: any; + }; + } } - } - ; - `, - ` - declare namespace JSX { - interface IntrinsicElements { - foo: { - bar: unknown; - }; + ; + `, + languageOptions: jsxLanguageOptions, + }, + { + code: noFormat` + declare namespace JSX { + interface IntrinsicElements { + foo: { + bar: unknown; + }; + } } - } - ; - `, + ; + `, + languageOptions: jsxLanguageOptions, + }, { - code: ` + code: noFormat` /** @deprecated */ function A() { return
; @@ -338,6 +354,7 @@ function A() { const a = ; `, + languageOptions: jsxLanguageOptions, options: [ { allow: [{ from: 'file', name: 'A' }], @@ -557,8 +574,10 @@ exists('/foo'); `, ], invalid: [ + // Skipped: React JSX deprecation requires React types (tsgolint parity) { - code: ` + skip: true, + code: noFormat` interface AProps { /** @deprecated */ b: number | string; @@ -570,6 +589,7 @@ exists('/foo'); const a = ; `, + languageOptions: jsxLanguageOptions, errors: [ { column: 22, @@ -1841,12 +1861,13 @@ exists('/foo'); ], }, { - code: ` + code: noFormat` /** @deprecated */ const A = () =>
; const a = ; `, + languageOptions: jsxLanguageOptions, errors: [ { column: 20, @@ -1859,12 +1880,13 @@ exists('/foo'); ], }, { - code: ` + code: noFormat` /** @deprecated */ const A = () =>
; const a = ; `, + languageOptions: jsxLanguageOptions, errors: [ { column: 20, @@ -1877,7 +1899,7 @@ exists('/foo'); ], }, { - code: ` + code: noFormat` /** @deprecated */ function A() { return
; @@ -1885,6 +1907,7 @@ exists('/foo'); const a = ; `, + languageOptions: jsxLanguageOptions, errors: [ { column: 20, @@ -1897,7 +1920,7 @@ exists('/foo'); ], }, { - code: ` + code: noFormat` /** @deprecated */ function A() { return
; @@ -1905,6 +1928,7 @@ exists('/foo'); const a = ; `, + languageOptions: jsxLanguageOptions, errors: [ { column: 20, @@ -2889,7 +2913,7 @@ class B extends A { ], }, { - code: ` + code: noFormat` class A { /** @deprecated test reason*/ constructor() {} @@ -2914,7 +2938,9 @@ class B extends A { ], }, { + skip: true, code: 'const a =
;', + languageOptions: jsxLanguageOptions, errors: [ { column: 16, @@ -2927,6 +2953,7 @@ class B extends A { ], }, { + skip: true, code: ` declare namespace JSX { interface IntrinsicElements { @@ -2942,6 +2969,7 @@ class B extends A { const componentDashed = ; `, + languageOptions: jsxLanguageOptions, errors: [ { column: 59, @@ -2954,7 +2982,8 @@ class B extends A { ], }, { - code: ` + skip: true, + code: noFormat` import * as React from 'react'; interface Props { @@ -2974,6 +3003,7 @@ class B extends A { const anotherExample = ; `, + languageOptions: jsxLanguageOptions, errors: [ { column: 42, From f60c6fd92221eaff3d88f1693cba088dd9de23ee Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Tue, 3 Mar 2026 15:34:46 -0800 Subject: [PATCH 2/7] fix: harden no-deprecated parity behavior --- cmd/rslint/api.go | 9 - .../rules/no_deprecated/no_deprecated.go | 230 +- .../__snapshots__/no-deprecated.test.ts.snap | 1938 +++++++++++++++++ .../rules/no-deprecated.test.ts | 15 + 4 files changed, 2107 insertions(+), 85 deletions(-) diff --git a/cmd/rslint/api.go b/cmd/rslint/api.go index e8d9a03d3..06dc9a589 100644 --- a/cmd/rslint/api.go +++ b/cmd/rslint/api.go @@ -135,15 +135,6 @@ func (h *IPCHandler) HandleLint(req api.LintRequest) (*api.LintResponse, error) }) } } - if len(rulesWithOptions) == 0 { - // Default to enabling all rules with empty options when none were specified. - for _, r := range rslintconfig.GlobalRuleRegistry.GetAllRules() { - rulesWithOptions = append(rulesWithOptions, RuleWithOption{ - rule: r, - option: []interface{}{}, - }) - } - } // Create compiler host host := utils.CreateCompilerHost(configDirectory, fs) diff --git a/internal/plugins/typescript/rules/no_deprecated/no_deprecated.go b/internal/plugins/typescript/rules/no_deprecated/no_deprecated.go index 23d6b5f2f..6b56e74c5 100644 --- a/internal/plugins/typescript/rules/no_deprecated/no_deprecated.go +++ b/internal/plugins/typescript/rules/no_deprecated/no_deprecated.go @@ -88,7 +88,7 @@ func getReportedNodeName(node *ast.Node) string { if node.Kind == ast.KindPrivateIdentifier { privateIdentifier := node.AsPrivateIdentifier() if privateIdentifier != nil { - return "#" + privateIdentifier.Text + return "#" + strings.TrimPrefix(privateIdentifier.Text, "#") } } return node.Text() @@ -511,15 +511,9 @@ func walkAst(node *ast.Node, visitor func(*ast.Node) bool) bool { if visitor(node) { return true } - shouldStop := false - node.ForEachChild(func(child *ast.Node) bool { - if walkAst(child, visitor) { - shouldStop = true - return true - } - return false + return node.ForEachChild(func(child *ast.Node) bool { + return walkAst(child, visitor) }) - return shouldStop } func moduleSpecifierText(node *ast.Node) string { @@ -1026,55 +1020,64 @@ func getDeprecationReason(ctx rule.RuleContext, node *ast.Node) (bool, string) { if node.Parent != nil && node.Kind != ast.KindSuperKeyword { parent := node.Parent if parent.Kind == ast.KindBindingElement { - bindingPattern := parent.Parent - if bindingPattern != nil && (bindingPattern.Kind == ast.KindObjectBindingPattern || bindingPattern.Kind == ast.KindArrayBindingPattern) { - sourceType := getBindingPatternSourceType(ctx, bindingPattern, map[*ast.Node]bool{}) - if sourceType == nil && bindingPattern.Kind == ast.KindObjectBindingPattern { - sourceType = ctx.TypeChecker.GetTypeAtLocation(bindingPattern) - } - if sourceType == nil { - sourceType = ctx.TypeChecker.GetTypeAtLocation(bindingPattern) - } - if sourceType != nil { - bindingElement := parent.AsBindingElement() - bindingNode := node - if bindingElement != nil && bindingElement.PropertyName != nil { - bindingNode = bindingElement.PropertyName - } - propertyName := "" - if bindingPattern.Kind == ast.KindArrayBindingPattern { - if bindingElement != nil { - if index, ok := bindingElementIndex(bindingElement); ok { - propertyName = strconv.Itoa(index) - } - } - } else { - if bindingName := bindingElementPropertyNameFromNode(parent); bindingName != "" { - propertyName = bindingName - } - if propertyName == "" && bindingElement != nil && bindingElement.PropertyName != nil { - if resolvedName, ok := resolveConstantPropertyName(ctx, bindingElement.PropertyName, 0, map[*ast.Symbol]bool{}); ok { - propertyName = resolvedName - } - } - if propertyName == "" { - propertyName = node.Text() + bindingElement := parent.AsBindingElement() + if bindingElement != nil { + isBindingTarget := bindingElement.Name() == node || + (bindingElement.PropertyName != nil && bindingElement.PropertyName == node) + if isBindingTarget { + bindingPattern := parent.Parent + if bindingPattern != nil && (bindingPattern.Kind == ast.KindObjectBindingPattern || bindingPattern.Kind == ast.KindArrayBindingPattern) { + sourceType := getBindingPatternSourceType(ctx, bindingPattern, map[*ast.Node]bool{}) + if sourceType == nil && bindingPattern.Kind == ast.KindObjectBindingPattern { + sourceType = ctx.TypeChecker.GetTypeAtLocation(bindingPattern) } - } - if propertyName != "" { - property := checker.Checker_getPropertyOfType(ctx.TypeChecker, sourceType, propertyName) - if isDeprecated, reason := getJsDocDeprecation(ctx.TypeChecker, property); isDeprecated { - return true, reason + if sourceType == nil { + sourceType = ctx.TypeChecker.GetTypeAtLocation(bindingPattern) } - if propertySymbol := ctx.TypeChecker.GetSymbolAtLocation(bindingNode); propertySymbol != nil { - if propertySymbol.ValueDeclaration != nil && propertySymbol.ValueDeclaration.Kind == ast.KindBindingElement { - propertySymbol = nil + if sourceType != nil { + bindingNode := node + if bindingElement.PropertyName != nil { + bindingNode = bindingElement.PropertyName } - if isDeprecated, reason := searchForDeprecationInAliasesChain(ctx.TypeChecker, propertySymbol, true); isDeprecated { - return true, reason + propertyName := "" + if bindingPattern.Kind == ast.KindArrayBindingPattern { + if index, ok := bindingElementIndex(bindingElement); ok { + propertyName = strconv.Itoa(index) + } + } else { + if bindingName := bindingElementPropertyName(bindingElement); bindingName != "" { + propertyName = bindingName + } + if propertyName == "" && bindingElement.PropertyName != nil { + if resolvedName, ok := resolveConstantPropertyName(ctx, bindingElement.PropertyName, 0, map[*ast.Symbol]bool{}); ok { + propertyName = resolvedName + } + } + if propertyName == "" { + propertyName = node.Text() + } } - if isDeprecated, reason := getJsDocDeprecation(ctx.TypeChecker, propertySymbol); isDeprecated { - return true, reason + if propertyName != "" { + property := checker.Checker_getPropertyOfType(ctx.TypeChecker, sourceType, propertyName) + if isDeprecated, reason := getJsDocDeprecation(ctx.TypeChecker, property); isDeprecated { + return true, reason + } + if propertySymbol := ctx.TypeChecker.GetSymbolAtLocation(bindingNode); propertySymbol != nil { + if propertySymbol.ValueDeclaration != nil && propertySymbol.ValueDeclaration.Kind == ast.KindBindingElement { + propertySymbol = nil + } + if isDeprecated, reason := searchForDeprecationInAliasesChain(ctx.TypeChecker, propertySymbol, true); isDeprecated { + return true, reason + } + if isDeprecated, reason := getJsDocDeprecation(ctx.TypeChecker, propertySymbol); isDeprecated { + return true, reason + } + } + if property != nil { + if isDeprecated, reason := searchForDeprecationInAliasesChain(ctx.TypeChecker, property, true); isDeprecated { + return true, reason + } + } } } } @@ -1185,19 +1188,36 @@ func isDynamicImportDefaultAccess(node *ast.Node, typeChecker *checker.Checker) if !isDynamicImportResultIdentifier(typeChecker.GetSymbolAtLocation(target)) { continue } - // Ignore only direct default access on the import result. - parent := access.AsNode().Parent - if parent != nil && parent.Kind == ast.KindPropertyAccessExpression { - parentAccess := parent.AsPropertyAccessExpression() - if parentAccess != nil && parentAccess.Expression == access.AsNode() && parentAccess.Name() != nil && parentAccess.Name().Text() == "default" { - return false - } - } return true } return false } +func isPromotedDynamicImportDefaultAccess(access *ast.PropertyAccessExpression, typeChecker *checker.Checker) bool { + if access == nil || typeChecker == nil || access.Name() == nil || access.Expression == nil { + return false + } + if access.Name().Text() != "default" { + return false + } + target := ast.SkipParentheses(access.Expression) + if target == nil || target.Kind != ast.KindIdentifier { + return false + } + if !isDynamicImportResultIdentifier(typeChecker.GetSymbolAtLocation(target)) { + return false + } + parent := access.AsNode().Parent + if parent == nil || parent.Kind != ast.KindPropertyAccessExpression { + return false + } + parentAccess := parent.AsPropertyAccessExpression() + return parentAccess != nil && + parentAccess.Expression == access.AsNode() && + parentAccess.Name() != nil && + parentAccess.Name().Text() == "default" +} + func shouldIgnoreDynamicImportDefault(node *ast.Node, pos int, end int, entityName string, typeChecker *checker.Checker) bool { if entityName != "default" || node == nil || typeChecker == nil { return false @@ -1214,9 +1234,20 @@ func shouldIgnoreDynamicImportDefault(node *ast.Node, pos int, end int, entityNa return false } if isDynamicImportResultIdentifier(typeChecker.GetSymbolAtLocation(target)) { + parent := access.AsNode().Parent + if parent != nil && parent.Kind == ast.KindPropertyAccessExpression { + parentAccess := parent.AsPropertyAccessExpression() + if parentAccess != nil && parentAccess.Expression == access.AsNode() && + parentAccess.Name() != nil && parentAccess.Name().Text() == "default" { + return false + } + } + return true + } + if access.Name() != nil && isDynamicImportResultIdentifier(typeChecker.GetSymbolAtLocation(access.Name())) { return true } - return isDynamicImportResultIdentifier(typeChecker.GetSymbolAtLocation(access.Name())) + return isDynamicImportDefaultAccess(node, typeChecker) } func promotedDynamicImportDefaultRange(node *ast.Node, pos int, end int, entityName string, typeChecker *checker.Checker) *core.TextRange { @@ -1244,6 +1275,12 @@ func promotedDynamicImportDefaultRange(node *ast.Node, pos int, end int, entityN if parentAccess.Name().Text() != "default" { return nil } + grandParent := parentAccess.AsNode().Parent + if grandParent != nil { + if grandParent.Kind == ast.KindPropertyAccessExpression || grandParent.Kind == ast.KindElementAccessExpression { + return nil + } + } promoted := core.NewTextRange(parentAccess.Name().Pos(), parentAccess.Name().End()) return &promoted } @@ -1577,7 +1614,10 @@ var NoDeprecatedRule = rule.CreateRule(rule.Rule{ return false } if bindingElement.Name() == node { - return bindingElement.PropertyName != nil + if bindingElement.PropertyName == nil { + return false + } + return bindingElement.PropertyName.Text() != node.Text() } return false case ast.KindClassExpression: @@ -1651,13 +1691,13 @@ var NoDeprecatedRule = rule.CreateRule(rule.Rule{ } return false } - reported := map[string]bool{} + reportedRanges := map[string]bool{} reportRange := func(diagnosticRange core.TextRange, message rule.RuleMessage) { - key := strconv.Itoa(diagnosticRange.Pos()) + ":" + strconv.Itoa(diagnosticRange.End()) + ":" + message.Id - if reported[key] { + key := strconv.Itoa(diagnosticRange.Pos()) + ":" + strconv.Itoa(diagnosticRange.End()) + if reportedRanges[key] { return } - reported[key] = true + reportedRanges[key] = true ctx.ReportRange(diagnosticRange, message) } if sourceFile != nil { @@ -1671,6 +1711,9 @@ var NoDeprecatedRule = rule.CreateRule(rule.Rule{ if shouldIgnoreDynamicImportDefault(node, diagnostic.Pos(), diagnostic.End(), name, ctx.TypeChecker) { continue } + if node != nil && (node.Kind == ast.KindIdentifier || node.Kind == ast.KindPrivateIdentifier || node.Kind == ast.KindSuperKeyword) { + continue + } symbol := symbolAtLocation(ctx.TypeChecker, node) if isWithinJsxClosingElement(node, diagnostic.Pos(), diagnostic.End()) { continue @@ -1702,6 +1745,18 @@ var NoDeprecatedRule = rule.CreateRule(rule.Rule{ } } } + deprecationNode := node + if node != nil && node.Kind == ast.KindVariableDeclaration { + variableDeclaration := node.AsVariableDeclaration() + if variableDeclaration != nil && variableDeclaration.Initializer != nil { + deprecationNode = ast.SkipParentheses(variableDeclaration.Initializer) + } + } + if message.Id != "deprecatedWithReason" { + if _, reason := getDeprecationReason(ctx, deprecationNode); reason != "" { + message = buildDeprecatedWithReasonMessage(name, reason) + } + } if message.Id != "deprecatedWithReason" { if reason := deprecatedReasonByNameInSource(ctx, name); reason != "" { message = buildDeprecatedWithReasonMessage(name, reason) @@ -1717,6 +1772,15 @@ var NoDeprecatedRule = rule.CreateRule(rule.Rule{ if isDeclaration(node) || isInsideImport(node) { return } + name := getReportedNodeName(node) + if name == "default" { + if access := propertyAccessForDiagnosticRange(node, node.Pos(), node.End()); isPromotedDynamicImportDefaultAccess(access, ctx.TypeChecker) { + return + } + } + if shouldIgnoreDynamicImportDefault(node, node.Pos(), node.End(), name, ctx.TypeChecker) { + return + } isDeprecated, reason := getDeprecationReason(ctx, node) if !isDeprecated { return @@ -1730,7 +1794,6 @@ var NoDeprecatedRule = rule.CreateRule(rule.Rule{ } // Only report if the deprecated symbol isn't just a local declaration in the current file. // Allow reporting for deprecated locals (for example, symbol usage). - name := getReportedNodeName(node) if shouldAllowDiagnostic(allowEntries, name, symbol, ctx.SourceFile) { return } @@ -1795,8 +1858,7 @@ var NoDeprecatedRule = rule.CreateRule(rule.Rule{ } checkIdentifier(node) }, - ast.KindPrivateIdentifier: checkIdentifier, - ast.KindSuperKeyword: checkIdentifier, + ast.KindSuperKeyword: checkIdentifier, ast.KindJsxAttribute: func(node *ast.Node) { jsxAttribute := node.AsJsxAttribute() if jsxAttribute == nil || jsxAttribute.Name() == nil { @@ -1872,21 +1934,37 @@ var NoDeprecatedRule = rule.CreateRule(rule.Rule{ if access == nil || access.Name() == nil { return } + if isPromotedDynamicImportDefaultAccess(access, ctx.TypeChecker) { + return + } // Report the property name if it is deprecated. nameNode := access.Name() + name := nameNode.Text() + if shouldIgnoreDynamicImportDefault(nameNode, nameNode.Pos(), nameNode.End(), name, ctx.TypeChecker) { + return + } propertySymbol := ctx.TypeChecker.GetSymbolAtLocation(nameNode) if !symbolIsDeprecated(ctx.TypeChecker, propertySymbol) { return } - name := nameNode.Text() if shouldAllowDiagnostic(allowEntries, name, propertySymbol, ctx.SourceFile) { return } + _, reason := getDeprecationReason(ctx, nameNode) message := buildDeprecatedMessage(name) - for _, declaration := range propertySymbol.Declarations { - if reason := deprecatedReasonFromDeclaration(declaration); reason != "" { - message = buildDeprecatedWithReasonMessage(name, reason) - break + if reason != "" { + message = buildDeprecatedWithReasonMessage(name, reason) + } else if propertySymbol != nil { + for _, declaration := range propertySymbol.Declarations { + if reasonFromDecl := deprecatedReasonFromDeclaration(declaration); reasonFromDecl != "" { + message = buildDeprecatedWithReasonMessage(name, reasonFromDecl) + break + } + } + } + if message.Id != "deprecatedWithReason" { + if reasonByName := deprecatedReasonByNameInSource(ctx, name); reasonByName != "" { + message = buildDeprecatedWithReasonMessage(name, reasonByName) } } trimmedRange := utils.TrimNodeTextRange(ctx.SourceFile, nameNode) diff --git a/packages/rslint-test-tools/tests/typescript-eslint/rules/__snapshots__/no-deprecated.test.ts.snap b/packages/rslint-test-tools/tests/typescript-eslint/rules/__snapshots__/no-deprecated.test.ts.snap index 13b57022b..dd1423092 100644 --- a/packages/rslint-test-tools/tests/typescript-eslint/rules/__snapshots__/no-deprecated.test.ts.snap +++ b/packages/rslint-test-tools/tests/typescript-eslint/rules/__snapshots__/no-deprecated.test.ts.snap @@ -2363,3 +2363,1941 @@ exports[`no-deprecated > invalid 73`] = ` "ruleCount": 1, } `; + +exports[`no-deprecated > invalid 74`] = ` +{ + "code": " + interface Props { + /** @deprecated */ + anchor: 'foo'; + } + declare const x: [item: Props]; + const [{ anchor = 'bar' }] = x; + ", + "diagnostics": [ + { + "message": "\`anchor\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 24, + "line": 7, + }, + "start": { + "column": 18, + "line": 7, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 75`] = ` +{ + "code": " + interface Props { + /** @deprecated */ + foo: Props; + } + declare const x: Props; + const { foo = x } = x; + ", + "diagnostics": [ + { + "message": "\`foo\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 20, + "line": 7, + }, + "start": { + "column": 17, + "line": 7, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 76`] = ` +{ + "code": " + import { DeprecatedClass } from './deprecated'; + + const foo = new DeprecatedClass(); + ", + "diagnostics": [ + { + "message": "\`DeprecatedClass\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 40, + "line": 4, + }, + "start": { + "column": 25, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 77`] = ` +{ + "code": " + import { DeprecatedClass } from './deprecated'; + + declare function inject(something: new () => unknown): void; + + inject(DeprecatedClass); + ", + "diagnostics": [ + { + "message": "\`DeprecatedClass\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 31, + "line": 6, + }, + "start": { + "column": 16, + "line": 6, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 78`] = ` +{ + "code": " + import { deprecatedVariable } from './deprecated'; + + const foo = deprecatedVariable; + ", + "diagnostics": [ + { + "message": "\`deprecatedVariable\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 39, + "line": 4, + }, + "start": { + "column": 21, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 79`] = ` +{ + "code": " + import { DeprecatedClass } from './deprecated'; + + declare const x: DeprecatedClass; + + const { foo } = x; + ", + "diagnostics": [ + { + "message": "\`DeprecatedClass\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 41, + "line": 4, + }, + "start": { + "column": 26, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + { + "message": "\`foo\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 20, + "line": 6, + }, + "start": { + "column": 17, + "line": 6, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 2, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 80`] = ` +{ + "code": " + import { deprecatedFunction } from './deprecated'; + + deprecatedFunction(); + ", + "diagnostics": [ + { + "message": "\`deprecatedFunction\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 27, + "line": 4, + }, + "start": { + "column": 9, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 81`] = ` +{ + "code": " + import * as imported from './deprecated'; + + const foo = new imported.NormalClass(); + ", + "diagnostics": [ + { + "message": "\`NormalClass\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 45, + "line": 4, + }, + "start": { + "column": 34, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 82`] = ` +{ + "code": " + import { NormalClass } from './deprecated'; + + const foo = new NormalClass(); + ", + "diagnostics": [ + { + "message": "\`NormalClass\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 36, + "line": 4, + }, + "start": { + "column": 25, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 83`] = ` +{ + "code": " + import * as imported from './deprecated'; + + const foo = imported.NormalClass; + ", + "diagnostics": [ + { + "message": "\`NormalClass\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 41, + "line": 4, + }, + "start": { + "column": 30, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 84`] = ` +{ + "code": " + import { NormalClass } from './deprecated'; + + const foo = NormalClass; + ", + "diagnostics": [ + { + "message": "\`NormalClass\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 32, + "line": 4, + }, + "start": { + "column": 21, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 85`] = ` +{ + "code": " + import { normalVariable } from './deprecated'; + + const foo = normalVariable; + ", + "diagnostics": [ + { + "message": "\`normalVariable\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 35, + "line": 4, + }, + "start": { + "column": 21, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 86`] = ` +{ + "code": " + import * as imported from './deprecated'; + + const foo = imported.normalVariable; + ", + "diagnostics": [ + { + "message": "\`normalVariable\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 44, + "line": 4, + }, + "start": { + "column": 30, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 87`] = ` +{ + "code": " + import * as imported from './deprecated'; + + const { normalVariable } = imported; + ", + "diagnostics": [ + { + "message": "\`normalVariable\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 31, + "line": 4, + }, + "start": { + "column": 17, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 88`] = ` +{ + "code": " + import { deprecatedVariable } from './deprecated'; + + const test = { + someField: deprecatedVariable, + }; + ", + "diagnostics": [ + { + "message": "\`deprecatedVariable\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 40, + "line": 5, + }, + "start": { + "column": 22, + "line": 5, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 89`] = ` +{ + "code": " + import { normalFunction } from './deprecated'; + + const foo = normalFunction; + ", + "diagnostics": [ + { + "message": "\`normalFunction\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 35, + "line": 4, + }, + "start": { + "column": 21, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 90`] = ` +{ + "code": " + import * as imported from './deprecated'; + + const foo = imported.normalFunction; + ", + "diagnostics": [ + { + "message": "\`normalFunction\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 44, + "line": 4, + }, + "start": { + "column": 30, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 91`] = ` +{ + "code": " + import * as imported from './deprecated'; + + const { normalFunction } = imported; + ", + "diagnostics": [ + { + "message": "\`normalFunction\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 31, + "line": 4, + }, + "start": { + "column": 17, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 92`] = ` +{ + "code": " + import { normalFunction } from './deprecated'; + + const foo = normalFunction(); + ", + "diagnostics": [ + { + "message": "\`normalFunction\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 35, + "line": 4, + }, + "start": { + "column": 21, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 93`] = ` +{ + "code": " + import * as imported from './deprecated'; + + const foo = imported.normalFunction(); + ", + "diagnostics": [ + { + "message": "\`normalFunction\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 44, + "line": 4, + }, + "start": { + "column": 30, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 94`] = ` +{ + "code": " + import { deprecatedFunctionWithOverloads } from './deprecated'; + + const foo = deprecatedFunctionWithOverloads('a'); + ", + "diagnostics": [ + { + "message": "\`deprecatedFunctionWithOverloads\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 52, + "line": 4, + }, + "start": { + "column": 21, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 95`] = ` +{ + "code": " + import * as imported from './deprecated'; + + const foo = imported.deprecatedFunctionWithOverloads('a'); + ", + "diagnostics": [ + { + "message": "\`imported.deprecatedFunctionWithOverloads\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 61, + "line": 4, + }, + "start": { + "column": 30, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 96`] = ` +{ + "code": " + import { reexportedDeprecatedFunctionWithOverloads } from './deprecated'; + + const foo = reexportedDeprecatedFunctionWithOverloads; + ", + "diagnostics": [ + { + "message": "\`reexportedDeprecatedFunctionWithOverloads\` is deprecated. Reason", + "messageId": "deprecatedWithReason", + "range": { + "end": { + "column": 62, + "line": 4, + }, + "start": { + "column": 21, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 97`] = ` +{ + "code": " + import * as imported from './deprecated'; + + const foo = imported.reexportedDeprecatedFunctionWithOverloads; + ", + "diagnostics": [ + { + "message": "\`reexportedDeprecatedFunctionWithOverloads\` is deprecated. Reason", + "messageId": "deprecatedWithReason", + "range": { + "end": { + "column": 71, + "line": 4, + }, + "start": { + "column": 30, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 98`] = ` +{ + "code": " + import * as imported from './deprecated'; + + const { reexportedDeprecatedFunctionWithOverloads } = imported; + ", + "diagnostics": [ + { + "message": "\`reexportedDeprecatedFunctionWithOverloads\` is deprecated. Reason", + "messageId": "deprecatedWithReason", + "range": { + "end": { + "column": 58, + "line": 4, + }, + "start": { + "column": 17, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 99`] = ` +{ + "code": " + import { reexportedDeprecatedFunctionWithOverloads } from './deprecated'; + + const foo = reexportedDeprecatedFunctionWithOverloads(); + ", + "diagnostics": [ + { + "message": "\`reexportedDeprecatedFunctionWithOverloads\` is deprecated. Reason", + "messageId": "deprecatedWithReason", + "range": { + "end": { + "column": 62, + "line": 4, + }, + "start": { + "column": 21, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 100`] = ` +{ + "code": " + import * as imported from './deprecated'; + + const foo = imported.reexportedDeprecatedFunctionWithOverloads(); + ", + "diagnostics": [ + { + "message": "\`reexportedDeprecatedFunctionWithOverloads\` is deprecated. Reason", + "messageId": "deprecatedWithReason", + "range": { + "end": { + "column": 71, + "line": 4, + }, + "start": { + "column": 30, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 101`] = ` +{ + "code": " + import { reexportedDeprecatedFunctionWithOverloads } from './deprecated'; + + const foo = reexportedDeprecatedFunctionWithOverloads('a'); + ", + "diagnostics": [ + { + "message": "\`reexportedDeprecatedFunctionWithOverloads\` is deprecated. Reason", + "messageId": "deprecatedWithReason", + "range": { + "end": { + "column": 62, + "line": 4, + }, + "start": { + "column": 21, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 102`] = ` +{ + "code": " + import * as imported from './deprecated'; + + const foo = imported.reexportedDeprecatedFunctionWithOverloads('a'); + ", + "diagnostics": [ + { + "message": "\`reexportedDeprecatedFunctionWithOverloads\` is deprecated. Reason", + "messageId": "deprecatedWithReason", + "range": { + "end": { + "column": 71, + "line": 4, + }, + "start": { + "column": 30, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 103`] = ` +{ + "code": " + import { ClassWithDeprecatedConstructor } from './deprecated'; + + const foo = new ClassWithDeprecatedConstructor('a'); + ", + "diagnostics": [ + { + "message": "\`ClassWithDeprecatedConstructor\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 55, + "line": 4, + }, + "start": { + "column": 25, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 104`] = ` +{ + "code": " + import * as imported from './deprecated'; + + const foo = new imported.ClassWithDeprecatedConstructor('a'); + ", + "diagnostics": [ + { + "message": "\`imported.ClassWithDeprecatedConstructor\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 64, + "line": 4, + }, + "start": { + "column": 34, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 105`] = ` +{ + "code": " + import { ReexportedClassWithDeprecatedConstructor } from './deprecated'; + + const foo = ReexportedClassWithDeprecatedConstructor; + ", + "diagnostics": [ + { + "message": "\`ReexportedClassWithDeprecatedConstructor\` is deprecated. Reason", + "messageId": "deprecatedWithReason", + "range": { + "end": { + "column": 61, + "line": 4, + }, + "start": { + "column": 21, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 106`] = ` +{ + "code": " + import * as imported from './deprecated'; + + const foo = imported.ReexportedClassWithDeprecatedConstructor; + ", + "diagnostics": [ + { + "message": "\`ReexportedClassWithDeprecatedConstructor\` is deprecated. Reason", + "messageId": "deprecatedWithReason", + "range": { + "end": { + "column": 70, + "line": 4, + }, + "start": { + "column": 30, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 107`] = ` +{ + "code": " + import * as imported from './deprecated'; + + const { ReexportedClassWithDeprecatedConstructor } = imported; + ", + "diagnostics": [ + { + "message": "\`ReexportedClassWithDeprecatedConstructor\` is deprecated. Reason", + "messageId": "deprecatedWithReason", + "range": { + "end": { + "column": 57, + "line": 4, + }, + "start": { + "column": 17, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 108`] = ` +{ + "code": " + import { ReexportedClassWithDeprecatedConstructor } from './deprecated'; + + const foo = ReexportedClassWithDeprecatedConstructor(); + ", + "diagnostics": [ + { + "message": "\`ReexportedClassWithDeprecatedConstructor\` is deprecated. Reason", + "messageId": "deprecatedWithReason", + "range": { + "end": { + "column": 61, + "line": 4, + }, + "start": { + "column": 21, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 109`] = ` +{ + "code": " + import * as imported from './deprecated'; + + const foo = imported.ReexportedClassWithDeprecatedConstructor(); + ", + "diagnostics": [ + { + "message": "\`ReexportedClassWithDeprecatedConstructor\` is deprecated. Reason", + "messageId": "deprecatedWithReason", + "range": { + "end": { + "column": 70, + "line": 4, + }, + "start": { + "column": 30, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 110`] = ` +{ + "code": " + import { ReexportedClassWithDeprecatedConstructor } from './deprecated'; + + const foo = ReexportedClassWithDeprecatedConstructor('a'); + ", + "diagnostics": [ + { + "message": "\`ReexportedClassWithDeprecatedConstructor\` is deprecated. Reason", + "messageId": "deprecatedWithReason", + "range": { + "end": { + "column": 61, + "line": 4, + }, + "start": { + "column": 21, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 111`] = ` +{ + "code": " + import * as imported from './deprecated'; + + const foo = imported.ReexportedClassWithDeprecatedConstructor('a'); + ", + "diagnostics": [ + { + "message": "\`ReexportedClassWithDeprecatedConstructor\` is deprecated. Reason", + "messageId": "deprecatedWithReason", + "range": { + "end": { + "column": 70, + "line": 4, + }, + "start": { + "column": 30, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 112`] = ` +{ + "code": " + import imported from './deprecated'; + + imported; + ", + "diagnostics": [ + { + "message": "\`imported\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 17, + "line": 4, + }, + "start": { + "column": 9, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 113`] = ` +{ + "code": " + async function fn() { + const d = await import('./deprecated.js'); + d.default.default; + } + ", + "diagnostics": [ + { + "message": "\`default\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 28, + "line": 4, + }, + "start": { + "column": 21, + "line": 4, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 114`] = ` +{ + "code": " + /** @deprecated */ + interface Foo {} + + class Bar implements Foo {} + ", + "diagnostics": [ + { + "message": "\`Foo\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 33, + "line": 5, + }, + "start": { + "column": 30, + "line": 5, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 115`] = ` +{ + "code": " + /** @deprecated */ + interface Foo {} + + export class Bar implements Foo {} + ", + "diagnostics": [ + { + "message": "\`Foo\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 40, + "line": 5, + }, + "start": { + "column": 37, + "line": 5, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 116`] = ` +{ + "code": " + /** @deprecated */ + interface Foo {} + + interface Baz {} + + export class Bar implements Baz, Foo {} + ", + "diagnostics": [ + { + "message": "\`Foo\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 45, + "line": 7, + }, + "start": { + "column": 42, + "line": 7, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 117`] = ` +{ + "code": " + /** @deprecated */ + class Foo {} + + export class Bar extends Foo {} + ", + "diagnostics": [ + { + "message": "\`Foo\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 37, + "line": 5, + }, + "start": { + "column": 34, + "line": 5, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 118`] = ` +{ + "code": " + /** @deprecated */ + declare function decorator(constructor: Function); + + @decorator + export class Foo {} + ", + "diagnostics": [ + { + "message": "\`decorator\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 19, + "line": 5, + }, + "start": { + "column": 10, + "line": 5, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 119`] = ` +{ + "code": " + /** @deprecated */ + function a(): object { + return {}; + } + + export default a(); + ", + "diagnostics": [ + { + "message": "\`a\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 25, + "line": 7, + }, + "start": { + "column": 24, + "line": 7, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 120`] = ` +{ + "code": " +class A { + /** @deprecated */ + constructor() {} +} + +class B extends A { + constructor() { + /** should report but does not */ + super(); + } +} + ", + "diagnostics": [ + { + "message": "\`new (): A\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 10, + "line": 10, + }, + "start": { + "column": 5, + "line": 10, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 121`] = ` +{ + "code": " +class A { + /** @deprecated test reason*/ + constructor() {} +} + +class B extends A { + constructor() { + /** should report but does not */ + super(); + } +} + ", + "diagnostics": [ + { + "message": "\`new (): A\` is deprecated. test reason", + "messageId": "deprecatedWithReason", + "range": { + "end": { + "column": 10, + "line": 10, + }, + "start": { + "column": 5, + "line": 10, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 122`] = ` +{ + "code": " +import { exists } from 'fs'; +exists('/foo'); + ", + "diagnostics": [ + { + "message": "\`exists\` is deprecated. Since v1.0.0 - Use {@link stat} or {@link access} instead.", + "messageId": "deprecatedWithReason", + "range": { + "end": { + "column": 7, + "line": 3, + }, + "start": { + "column": 1, + "line": 3, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 123`] = ` +{ + "code": " + declare class A { + /** @deprecated */ + accessor b: () => string; + } + + declare const a: A; + + a.b; + ", + "diagnostics": [ + { + "message": "\`b\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 12, + "line": 9, + }, + "start": { + "column": 11, + "line": 9, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 124`] = ` +{ + "code": " + declare class A { + /** @deprecated */ + accessor b: () => string; + } + + declare const a: A; + + a.b(); + ", + "diagnostics": [ + { + "message": "\`b\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 12, + "line": 9, + }, + "start": { + "column": 11, + "line": 9, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 125`] = ` +{ + "code": " + class A { + /** @deprecated */ + accessor b = (): string => { + return ''; + }; + } + + declare const a: A; + + a.b(); + ", + "diagnostics": [ + { + "message": "\`b\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 12, + "line": 11, + }, + "start": { + "column": 11, + "line": 11, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 126`] = ` +{ + "code": " + declare class A { + /** @deprecated */ + static accessor b: () => string; + } + + A.b(); + ", + "diagnostics": [ + { + "message": "\`b\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 12, + "line": 7, + }, + "start": { + "column": 11, + "line": 7, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 127`] = ` +{ + "code": " + class A { + /** @deprecated */ + #b = () => {}; + + c() { + this.#b(); + } + } + ", + "diagnostics": [ + { + "message": "\`#b\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 20, + "line": 7, + }, + "start": { + "column": 18, + "line": 7, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 128`] = ` +{ + "code": " + const a = { + /** @deprecated */ + b: 'string', + }; + + const c = a['b']; + ", + "diagnostics": [ + { + "message": "\`b\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 24, + "line": 7, + }, + "start": { + "column": 21, + "line": 7, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 129`] = ` +{ + "code": " + const a = { + /** @deprecated */ + b: 'string', + }; + const x = 'b'; + const c = a[x]; + ", + "diagnostics": [ + { + "message": "\`b\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 22, + "line": 7, + }, + "start": { + "column": 21, + "line": 7, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 130`] = ` +{ + "code": " + const a = { + /** @deprecated */ + [2]: 'string', + }; + const x = 'b'; + const c = a[2]; + ", + "diagnostics": [ + { + "message": "\`2\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 22, + "line": 7, + }, + "start": { + "column": 21, + "line": 7, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 131`] = ` +{ + "code": " + const a = { + /** @deprecated reason for deprecation */ + b: 'string', + }; + + const key = 'b'; + const stringKey = key as const; + const c = a[stringKey]; + ", + "diagnostics": [ + { + "message": "\`b\` is deprecated. reason for deprecation", + "messageId": "deprecatedWithReason", + "range": { + "end": { + "column": 30, + "line": 9, + }, + "start": { + "column": 21, + "line": 9, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 132`] = ` +{ + "code": " + enum Keys { + B = 'b', + } + + const a = { + /** @deprecated reason for deprecation */ + b: 'string', + }; + + const key = Keys.B; + const c = a[key]; + ", + "diagnostics": [ + { + "message": "\`b\` is deprecated. reason for deprecation", + "messageId": "deprecatedWithReason", + "range": { + "end": { + "column": 24, + "line": 12, + }, + "start": { + "column": 21, + "line": 12, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 133`] = ` +{ + "code": " + const a = { + /** @deprecated */ + b: 'string', + }; + + const key = \`b\`; + const c = a[key]; + ", + "diagnostics": [ + { + "message": "\`b\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 24, + "line": 8, + }, + "start": { + "column": 21, + "line": 8, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; + +exports[`no-deprecated > invalid 134`] = ` +{ + "code": " + const stringObj = 'b'; + const a = { + /** @deprecated */ + b: 'string', + }; + const c = a[stringObj]; + ", + "diagnostics": [ + { + "message": "\`b\` is deprecated.", + "messageId": "deprecated", + "range": { + "end": { + "column": 30, + "line": 7, + }, + "start": { + "column": 21, + "line": 7, + }, + }, + "ruleName": "@typescript-eslint/no-deprecated", + }, + ], + "errorCount": 1, + "fileCount": 1, + "ruleCount": 1, +} +`; diff --git a/packages/rslint-test-tools/tests/typescript-eslint/rules/no-deprecated.test.ts b/packages/rslint-test-tools/tests/typescript-eslint/rules/no-deprecated.test.ts index 16cca2723..5ba501ce4 100644 --- a/packages/rslint-test-tools/tests/typescript-eslint/rules/no-deprecated.test.ts +++ b/packages/rslint-test-tools/tests/typescript-eslint/rules/no-deprecated.test.ts @@ -272,6 +272,21 @@ ruleTester.run('no-deprecated', { }, }, }, + { + code: ` + async function fn() { + const d = await import('./deprecated.js'); + d.default['foo']; + } + `, + languageOptions: { + parserOptions: { + project: './tsconfig.moduleResolution-node16.json', + projectService: false, + tsconfigRootDir: rootDir, + }, + }, + }, 'call();', // this test is to ensure the rule doesn't crash when class implements itself From 17cb705d076c8e70b97ce1502cb4296322b3407c Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Tue, 3 Mar 2026 17:48:08 -0800 Subject: [PATCH 3/7] test: cover no-deprecated allow from:file import behavior --- .../rules/no_deprecated/no_deprecated_test.go | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/internal/plugins/typescript/rules/no_deprecated/no_deprecated_test.go b/internal/plugins/typescript/rules/no_deprecated/no_deprecated_test.go index d84e48076..1a5cc7f30 100644 --- a/internal/plugins/typescript/rules/no_deprecated/no_deprecated_test.go +++ b/internal/plugins/typescript/rules/no_deprecated/no_deprecated_test.go @@ -1,11 +1,18 @@ package no_deprecated import ( + "sync" "testing" "github.com/microsoft/typescript-go/shim/ast" + "github.com/microsoft/typescript-go/shim/bundled" + "github.com/microsoft/typescript-go/shim/compiler" "github.com/microsoft/typescript-go/shim/tspath" + "github.com/microsoft/typescript-go/shim/vfs/cachedvfs" + "github.com/microsoft/typescript-go/shim/vfs/osvfs" + "github.com/web-infra-dev/rslint/internal/linter" "github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/fixtures" + "github.com/web-infra-dev/rslint/internal/rule" "github.com/web-infra-dev/rslint/internal/rule_tester" "github.com/web-infra-dev/rslint/internal/utils" ) @@ -139,3 +146,97 @@ oldValue; }, }) } + +func runNoDeprecatedDiagnosticsForFiles(t *testing.T, files map[string]string, entryFile string, options any) []rule.RuleDiagnostic { + t.Helper() + rootDir := fixtures.GetRootDir() + virtualFiles := make(map[string]string, len(files)) + for fileName, content := range files { + virtualFiles[tspath.ResolvePath(rootDir, fileName)] = content + } + fs := utils.NewOverlayVFS(bundled.WrapFS(cachedvfs.From(osvfs.FS())), virtualFiles) + host := utils.CreateCompilerHost(rootDir, fs) + program, err := utils.CreateProgram(true, fs, rootDir, "tsconfig.json", host) + if err != nil { + t.Fatalf("failed to create program: %v", err) + } + sourceFile := program.GetSourceFile(entryFile) + if sourceFile == nil { + t.Fatalf("failed to resolve entry file: %s", entryFile) + } + diagnostics := []rule.RuleDiagnostic{} + var diagnosticsMu sync.Mutex + _, err = linter.RunLinter( + []*compiler.Program{program}, + true, + []string{sourceFile.FileName()}, + []string{}, + func(_ *ast.SourceFile) []linter.ConfiguredRule { + return []linter.ConfiguredRule{ + { + Name: "test", + Severity: rule.SeverityError, + Run: func(ctx rule.RuleContext) rule.RuleListeners { + return NoDeprecatedRule.Run(ctx, options) + }, + }, + } + }, + func(diagnostic rule.RuleDiagnostic) { + diagnosticsMu.Lock() + diagnostics = append(diagnostics, diagnostic) + diagnosticsMu.Unlock() + }, + ) + if err != nil { + t.Fatalf("error running linter: %v", err) + } + return diagnostics +} + +func TestAllowFromFileImportedValue(t *testing.T) { + t.Parallel() + files := map[string]string{ + "deprecated.ts": ` +/** @deprecated */ +export const oldValue = 1; + `, + "main.ts": ` +import { oldValue } from './deprecated'; +oldValue; + `, + } + + t.Run("allow-from-file-suppresses-imported-value", func(t *testing.T) { + t.Parallel() + diagnostics := runNoDeprecatedDiagnosticsForFiles( + t, + files, + "main.ts", + []interface{}{ + map[string]interface{}{ + "allow": []interface{}{ + map[string]interface{}{ + "from": "file", + "name": "oldValue", + }, + }, + }, + }, + ) + if len(diagnostics) != 0 { + t.Fatalf("expected no diagnostics, got %d", len(diagnostics)) + } + }) + + t.Run("without-allow-reports-imported-value", func(t *testing.T) { + t.Parallel() + diagnostics := runNoDeprecatedDiagnosticsForFiles(t, files, "main.ts", nil) + if len(diagnostics) != 1 { + t.Fatalf("expected 1 diagnostic, got %d", len(diagnostics)) + } + if diagnostics[0].Message.Id != "deprecated" { + t.Fatalf("expected message id deprecated, got %s", diagnostics[0].Message.Id) + } + }) +} From 3c108ce89f4cd707a4d0b4d86a866f5aae462c66 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Tue, 3 Mar 2026 20:52:57 -0800 Subject: [PATCH 4/7] fix: resolve no-deprecated ci regressions --- .../typescript/rules/no_deprecated/no_deprecated.go | 13 +------------ packages/rslint/src/browser.ts | 6 +++--- packages/rslint/src/node.ts | 6 +++--- 3 files changed, 7 insertions(+), 18 deletions(-) diff --git a/internal/plugins/typescript/rules/no_deprecated/no_deprecated.go b/internal/plugins/typescript/rules/no_deprecated/no_deprecated.go index 6b56e74c5..5c651c204 100644 --- a/internal/plugins/typescript/rules/no_deprecated/no_deprecated.go +++ b/internal/plugins/typescript/rules/no_deprecated/no_deprecated.go @@ -62,10 +62,7 @@ func isNodeCalleeOfParent(node *ast.Node) bool { func getCallLikeNode(node *ast.Node) *ast.Node { callee := node - for { - if callee == nil || callee.Parent == nil || callee.Parent.Kind != ast.KindPropertyAccessExpression { - break - } + for callee != nil && callee.Parent != nil && callee.Parent.Kind == ast.KindPropertyAccessExpression { parentAccess := callee.Parent.AsPropertyAccessExpression() if parentAccess == nil || parentAccess.Name() == nil || parentAccess.Name().AsNode() != callee { break @@ -917,14 +914,6 @@ func getJsxAttributeDeprecation(ctx rule.RuleContext, elementNode *ast.Node, pro return getJsDocDeprecation(ctx.TypeChecker, symbol) } -func bindingElementPropertyNameFromNode(node *ast.Node) string { - if node == nil || node.Kind != ast.KindBindingElement { - return "" - } - bindingElement := node.AsBindingElement() - return bindingElementPropertyName(bindingElement) -} - func getBindingPatternSourceType(ctx rule.RuleContext, bindingPattern *ast.Node, seen map[*ast.Node]bool) *checker.Type { if ctx.TypeChecker == nil || bindingPattern == nil { return nil diff --git a/packages/rslint/src/browser.ts b/packages/rslint/src/browser.ts index 12f144abe..1d7a8b37c 100644 --- a/packages/rslint/src/browser.ts +++ b/packages/rslint/src/browser.ts @@ -86,7 +86,7 @@ export class BrowserRslintService implements RslintServiceInterface { this.expectedSize = dataView.getUint32(0, true); // true for little-endian // Remove length bytes from buffer - this.chunks = [combined.slice(4)]; + this.chunks = [combined.subarray(4)]; this.chunkSize -= 4; } @@ -95,7 +95,7 @@ export class BrowserRslintService implements RslintServiceInterface { // Read the message content const combined = this.combineChunks(); - const messageBytes = combined.slice(0, this.expectedSize); + const messageBytes = combined.subarray(0, this.expectedSize); const message = new TextDecoder().decode(messageBytes); // Handle the message @@ -107,7 +107,7 @@ export class BrowserRslintService implements RslintServiceInterface { } // Reset for next message - this.chunks = [combined.slice(this.expectedSize)]; + this.chunks = [combined.subarray(this.expectedSize)]; this.chunkSize = this.chunks[0].length; this.expectedSize = null; } diff --git a/packages/rslint/src/node.ts b/packages/rslint/src/node.ts index b5c779a11..e49d05914 100644 --- a/packages/rslint/src/node.ts +++ b/packages/rslint/src/node.ts @@ -86,7 +86,7 @@ export class NodeRslintService implements RslintServiceInterface { this.expectedSize = combined.readUInt32LE(0); // Remove length bytes from buffer - this.chunks = [combined.slice(4)]; + this.chunks = [combined.subarray(4)]; this.chunkSize -= 4; } @@ -95,7 +95,7 @@ export class NodeRslintService implements RslintServiceInterface { // Read the message content const combined = Buffer.concat(this.chunks); - const message = combined.slice(0, this.expectedSize).toString('utf8'); + const message = combined.subarray(0, this.expectedSize).toString('utf8'); // Handle the message try { @@ -106,7 +106,7 @@ export class NodeRslintService implements RslintServiceInterface { } // Reset for next message - this.chunks = [combined.slice(this.expectedSize)]; + this.chunks = [combined.subarray(this.expectedSize)]; this.chunkSize = this.chunks[0].length; this.expectedSize = null; } From 1dcf062920b912cbf7a4f80e62f849fa268d77f3 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Tue, 3 Mar 2026 22:08:16 -0800 Subject: [PATCH 5/7] fix: satisfy spell check in no-deprecated rule --- .../typescript/rules/no_deprecated/no_deprecated.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/plugins/typescript/rules/no_deprecated/no_deprecated.go b/internal/plugins/typescript/rules/no_deprecated/no_deprecated.go index 5c651c204..1356b2592 100644 --- a/internal/plugins/typescript/rules/no_deprecated/no_deprecated.go +++ b/internal/plugins/typescript/rules/no_deprecated/no_deprecated.go @@ -95,8 +95,8 @@ func getJsDocDeprecationFromNode(node *ast.Node) string { if node == nil { return "" } - jsdocs := node.JSDoc(nil) - for _, jsdoc := range jsdocs { + jsDocs := node.JSDoc(nil) + for _, jsdoc := range jsDocs { tags := jsdoc.AsJSDoc().Tags if tags == nil { continue @@ -123,8 +123,8 @@ func hasDeprecatedTag(node *ast.Node) bool { if node == nil { return false } - jsdocs := node.JSDoc(nil) - for _, jsdoc := range jsdocs { + jsDocs := node.JSDoc(nil) + for _, jsdoc := range jsDocs { tags := jsdoc.AsJSDoc().Tags if tags == nil { continue @@ -1831,8 +1831,8 @@ var NoDeprecatedRule = rule.CreateRule(rule.Rule{ if isPropertyName { return } - jsdocs := node.Parent.JSDoc(nil) - for _, jsdoc := range jsdocs { + jsDocs := node.Parent.JSDoc(nil) + for _, jsdoc := range jsDocs { tags := jsdoc.AsJSDoc().Tags if tags == nil { continue From 35fafbae5ff241376a58ede9f16df1f1e95f3878 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Mon, 16 Mar 2026 12:16:19 -0700 Subject: [PATCH 6/7] fix: tighten no-deprecated from-file allow matching --- .../typescript/rules/no_deprecated/no_deprecated.go | 4 ++++ .../typescript/rules/no_deprecated/no_deprecated_test.go | 9 ++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/internal/plugins/typescript/rules/no_deprecated/no_deprecated.go b/internal/plugins/typescript/rules/no_deprecated/no_deprecated.go index 1356b2592..6e35364fd 100644 --- a/internal/plugins/typescript/rules/no_deprecated/no_deprecated.go +++ b/internal/plugins/typescript/rules/no_deprecated/no_deprecated.go @@ -470,6 +470,10 @@ func declarationInCurrentFile(symbol *ast.Symbol, sourceFile *ast.SourceFile) bo if declaration == nil { continue } + switch declaration.Kind { + case ast.KindImportSpecifier, ast.KindImportClause, ast.KindNamespaceImport, ast.KindImportEqualsDeclaration: + continue + } if ast.GetSourceFileOfNode(declaration) == sourceFile { return true } diff --git a/internal/plugins/typescript/rules/no_deprecated/no_deprecated_test.go b/internal/plugins/typescript/rules/no_deprecated/no_deprecated_test.go index 1a5cc7f30..475708b97 100644 --- a/internal/plugins/typescript/rules/no_deprecated/no_deprecated_test.go +++ b/internal/plugins/typescript/rules/no_deprecated/no_deprecated_test.go @@ -207,7 +207,7 @@ oldValue; `, } - t.Run("allow-from-file-suppresses-imported-value", func(t *testing.T) { + t.Run("allow-from-file-does-not-suppress-imported-value", func(t *testing.T) { t.Parallel() diagnostics := runNoDeprecatedDiagnosticsForFiles( t, @@ -224,8 +224,11 @@ oldValue; }, }, ) - if len(diagnostics) != 0 { - t.Fatalf("expected no diagnostics, got %d", len(diagnostics)) + if len(diagnostics) != 1 { + t.Fatalf("expected 1 diagnostic, got %d", len(diagnostics)) + } + if diagnostics[0].Message.Id != "deprecated" { + t.Fatalf("expected message id deprecated, got %s", diagnostics[0].Message.Id) } }) From b1a05e7ac90475394725724644a52bed197e5474 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Mon, 25 May 2026 16:08:32 -0700 Subject: [PATCH 7/7] fix: harden no-deprecated parity --- cmd/rslint/api.go | 22 +- .../rules/no_deprecated/no_deprecated.go | 389 +++++++++++++++++- .../rules/no_deprecated/no_deprecated_test.go | 22 + 3 files changed, 399 insertions(+), 34 deletions(-) diff --git a/cmd/rslint/api.go b/cmd/rslint/api.go index 3773096f1..df2209e70 100644 --- a/cmd/rslint/api.go +++ b/cmd/rslint/api.go @@ -153,14 +153,6 @@ func (h *IPCHandler) HandleLint(req api.LintRequest) (*api.LintResponse, error) }) } } - } else { - // Default to enabling all rules with empty options when none were specified. - for _, r := range rslintconfig.GlobalRuleRegistry.GetAllRules() { - rulesWithOptions = append(rulesWithOptions, RuleWithOption{ - rule: r, - option: []interface{}{}, - }) - } } // Create compiler host @@ -251,16 +243,20 @@ func (h *IPCHandler) HandleLint(req api.LintRequest) (*api.LintResponse, error) sourceFiles[filePath] = sourceFile sourceFilesLock.Unlock() + if len(req.RuleOptions) == 0 { + rules, _ := rslintconfig.GlobalRuleRegistry.GetEnabledRules(rslintConfig, sourceFile.FileName(), configDirectory, false) + return rules + } + var settings map[string]interface{} if merged := rslintConfig.GetConfigForFile(sourceFile.FileName(), configDirectory); merged != nil && len(merged.Settings) > 0 { settings = rslintconfig.CloneSettings(merged.Settings) } - return utils.Map(rulesWithOptions, func(r RuleWithOption) linter.ConfiguredRule { - return linter.ConfiguredRule{ - Name: r.rule.Name, - Settings: settings, + Name: r.rule.Name, + Settings: settings, + RequiresTypeInfo: r.rule.RequiresTypeInfo, Run: func(ctx rule.RuleContext) rule.RuleListeners { return r.rule.Run(ctx, r.option) }, @@ -292,7 +288,7 @@ func (h *IPCHandler) HandleLint(req api.LintRequest) (*api.LintResponse, error) Diagnostics: diagnostics, ErrorCount: errorsCount, FileCount: int(lintResult.LintedFileCount), - RuleCount: len(rulesWithOptions), + RuleCount: len(lintResult.ExecutedRules), } // Only include encoded source files if requested if req.IncludeEncodedSourceFiles { diff --git a/internal/plugins/typescript/rules/no_deprecated/no_deprecated.go b/internal/plugins/typescript/rules/no_deprecated/no_deprecated.go index 6e35364fd..e5fa4777b 100644 --- a/internal/plugins/typescript/rules/no_deprecated/no_deprecated.go +++ b/internal/plugins/typescript/rules/no_deprecated/no_deprecated.go @@ -142,7 +142,14 @@ func hasDeprecatedTagInSource(node *ast.Node) bool { if node == nil { return false } - sourceFile := ast.GetSourceFileOfNode(node) + declarationNode := node + if node.Parent != nil && node.Parent.Kind == ast.KindVariableDeclaration { + variableDeclaration := node.Parent.AsVariableDeclaration() + if variableDeclaration != nil && variableDeclaration.Name() == node { + declarationNode = node.Parent + } + } + sourceFile := ast.GetSourceFileOfNode(declarationNode) if sourceFile == nil { return false } @@ -151,13 +158,16 @@ func hasDeprecatedTagInSource(node *ast.Node) bool { return false } - anchor := node.Pos() - if node.Kind == ast.KindVariableDeclaration && node.Parent != nil && node.Parent.Parent != nil { - anchor = node.Parent.Parent.Pos() + anchor := declarationNode.Pos() + if declarationNode.Kind == ast.KindVariableDeclaration && declarationNode.Parent != nil && declarationNode.Parent.Parent != nil { + anchor = declarationNode.Parent.Parent.Pos() } - if anchor <= 0 || anchor > len(text) { + if anchor < 0 || anchor > len(text) { return false } + if declarationNode.Kind == ast.KindVariableDeclaration && hasDeprecatedLeadingCommentAt(text, anchor) { + return true + } // Walk backwards over whitespace to find a directly preceding JSDoc comment. i := anchor - 1 for i >= 0 { @@ -183,6 +193,23 @@ func hasDeprecatedTagInSource(node *ast.Node) bool { return deprecatedReasonPattern.MatchString(comment) } +func hasDeprecatedLeadingCommentAt(text string, anchor int) bool { + if anchor < 0 || anchor >= len(text) { + return false + } + snippet := text[anchor:] + trimmed := strings.TrimLeft(snippet, " \t\n\r") + if !strings.HasPrefix(trimmed, "/**") { + return false + } + commentEnd := strings.Index(trimmed, "*/") + if commentEnd < 0 { + return false + } + comment := trimmed[:commentEnd+2] + return deprecatedReasonPattern.MatchString(comment) +} + func getJsDocDeprecation(typeChecker *checker.Checker, symbol *ast.Symbol) (bool, string) { if typeChecker == nil || symbol == nil { return false, "" @@ -1570,6 +1597,163 @@ func deprecatedInfoByNameInSource(ctx rule.RuleContext, name string, propertyOnl return found, reason } +func deprecatedVariableInfoByNameInSource(ctx rule.RuleContext, name string) (bool, string) { + if ctx.TypeChecker == nil || ctx.SourceFile == nil || name == "" { + return false, "" + } + targetName := normalizeComparableName(name) + if targetName == "" { + return false, "" + } + found := false + reason := "" + walkAst(ctx.SourceFile.AsNode(), func(node *ast.Node) bool { + if node == nil || node.Kind != ast.KindVariableDeclaration { + return false + } + nameNode := declarationNameNode(node) + if nameNode == nil || normalizeComparableName(nodeNameText(nameNode)) != targetName { + return false + } + if !ctx.TypeChecker.IsDeprecatedDeclaration(node) && !hasDeprecatedTag(node) && !hasDeprecatedTagInSource(node) { + return false + } + found = true + if reason == "" { + reason = deprecatedReasonFromDeclaration(node) + } + return reason != "" + }) + return found, reason +} + +func deprecatedStructuralPropertyInfoByNameInSource(ctx rule.RuleContext, name string) (bool, string) { + if ctx.TypeChecker == nil || ctx.SourceFile == nil || name == "" { + return false, "" + } + targetName := normalizeComparableName(name) + if targetName == "" { + return false, "" + } + found := false + reason := "" + walkAst(ctx.SourceFile.AsNode(), func(node *ast.Node) bool { + if node == nil { + return false + } + if node.Kind != ast.KindPropertyAssignment && + node.Kind != ast.KindPropertySignature && + node.Kind != ast.KindPropertyDeclaration { + return false + } + nameNode := declarationNameNode(node) + if nameNode == nil || normalizeComparableName(nodeNameText(nameNode)) != targetName { + return false + } + if !ctx.TypeChecker.IsDeprecatedDeclaration(node) && !hasDeprecatedTag(node) && !hasDeprecatedTagInSource(node) { + return false + } + found = true + if reason == "" { + reason = deprecatedReasonFromDeclaration(node) + } + return reason != "" + }) + return found, reason +} + +func deprecatedMethodInfoByNameInSource(ctx rule.RuleContext, name string, argCount int, matchArgCount bool) (bool, string) { + if ctx.TypeChecker == nil || ctx.SourceFile == nil || name == "" { + return false, "" + } + targetName := normalizeComparableName(name) + if targetName == "" { + return false, "" + } + candidates := []*ast.Node{} + hasSignature := false + walkAst(ctx.SourceFile.AsNode(), func(node *ast.Node) bool { + if node == nil || (node.Kind != ast.KindMethodDeclaration && node.Kind != ast.KindMethodSignature) { + return false + } + nameNode := declarationNameNode(node) + if nameNode == nil || normalizeComparableName(nodeNameText(nameNode)) != targetName { + return false + } + if matchArgCount && len(node.Parameters()) != argCount { + return false + } + candidates = append(candidates, node) + if node.Body() == nil { + hasSignature = true + } + return false + }) + found := false + allDeprecated := true + reason := "" + for _, node := range candidates { + if hasSignature && node.Body() != nil { + continue + } + found = true + if !ctx.TypeChecker.IsDeprecatedDeclaration(node) && !hasDeprecatedTag(node) && !hasDeprecatedTagInSource(node) { + allDeprecated = false + continue + } + if reason == "" { + reason = deprecatedReasonFromDeclaration(node) + } + } + return found && allDeprecated, reason +} + +func deprecatedFunctionInfoByNameInSource(ctx rule.RuleContext, name string, argCount int, matchArgCount bool) (bool, string) { + if ctx.TypeChecker == nil || ctx.SourceFile == nil || name == "" { + return false, "" + } + targetName := normalizeComparableName(name) + if targetName == "" { + return false, "" + } + candidates := []*ast.Node{} + hasSignature := false + walkAst(ctx.SourceFile.AsNode(), func(node *ast.Node) bool { + if node == nil || node.Kind != ast.KindFunctionDeclaration { + return false + } + nameNode := declarationNameNode(node) + if nameNode == nil || normalizeComparableName(nodeNameText(nameNode)) != targetName { + return false + } + if matchArgCount && len(node.Parameters()) != argCount { + return false + } + candidates = append(candidates, node) + if node.Body() == nil { + hasSignature = true + } + return false + }) + found := false + allDeprecated := true + reason := "" + for _, node := range candidates { + if hasSignature && node.Body() != nil { + continue + } + found = true + if !ctx.TypeChecker.IsDeprecatedDeclaration(node) && !hasDeprecatedTag(node) && !hasDeprecatedTagInSource(node) { + allDeprecated = false + continue + } + if reason == "" { + reason = deprecatedReasonFromDeclaration(node) + } + } + return found && allDeprecated, reason +} + func deprecatedReasonByNameInSource(ctx rule.RuleContext, name string) string { _, reason := deprecatedInfoByNameInSource(ctx, name, false) return reason @@ -1579,8 +1763,133 @@ func deprecatedPropertyInfoByNameInSource(ctx rule.RuleContext, name string) (bo return deprecatedInfoByNameInSource(ctx, name, true) } +func shouldUseDeprecatedVariableSourceFallback(node *ast.Node) bool { + if node == nil || node.Parent == nil { + return false + } + switch node.Parent.Kind { + case ast.KindPropertyAccessExpression: + access := node.Parent.AsPropertyAccessExpression() + return access == nil || access.Name() == nil || access.Name().AsNode() != node + case ast.KindElementAccessExpression, ast.KindJsxAttribute: + return false + default: + return true + } +} + +func isPropertyAccessName(node *ast.Node) bool { + if node == nil || node.Parent == nil || node.Parent.Kind != ast.KindPropertyAccessExpression { + return false + } + access := node.Parent.AsPropertyAccessExpression() + return access != nil && access.Name() != nil && access.Name().AsNode() == node +} + +func isBindingElementNameOrProperty(node *ast.Node) bool { + if node == nil || node.Parent == nil || node.Parent.Kind != ast.KindBindingElement { + return false + } + bindingElement := node.Parent.AsBindingElement() + if bindingElement == nil { + return false + } + return bindingElement.Name() == node || bindingElement.PropertyName == node +} + +func isPropertyAccessCalleeName(node *ast.Node) bool { + if !isPropertyAccessName(node) || node.Parent.Parent == nil { + return false + } + if node.Parent.Parent.Kind != ast.KindCallExpression { + return false + } + callExpression := node.Parent.Parent.AsCallExpression() + return callExpression != nil && callExpression.Expression == node.Parent +} + +func propertyAccessDisplayName(sourceFile *ast.SourceFile, node *ast.Node) string { + if !isPropertyAccessName(node) { + return getReportedNodeName(node) + } + text := strings.TrimSpace(sourceSpanText(sourceFile, node.Parent.Pos(), node.Parent.End())) + if text == "" { + return getReportedNodeName(node) + } + return text +} + +func propertyAccessCallArgCount(node *ast.Node) (int, bool) { + if !isPropertyAccessCalleeName(node) { + return 0, false + } + callExpression := node.Parent.Parent.AsCallExpression() + if callExpression == nil || callExpression.Arguments == nil { + return 0, true + } + return len(callExpression.Arguments.Nodes), true +} + +func callArgCountForIdentifierCallee(node *ast.Node) (int, bool) { + if node == nil || node.Parent == nil || node.Parent.Kind != ast.KindCallExpression { + return 0, false + } + callExpression := node.Parent.AsCallExpression() + if callExpression == nil || callExpression.Expression != node { + return 0, false + } + if callExpression.Arguments == nil { + return 0, true + } + return len(callExpression.Arguments.Nodes), true +} + +func isIdentifierTaggedTemplateTag(node *ast.Node) bool { + if node == nil || node.Parent == nil || node.Parent.Kind != ast.KindTaggedTemplateExpression { + return false + } + taggedTemplate := node.Parent.AsTaggedTemplateExpression() + return taggedTemplate != nil && taggedTemplate.Tag == node +} + +func isDeprecatedNodeAssertFailOverload(ctx rule.RuleContext, node *ast.Node, name string) (bool, string) { + if ctx.SourceFile == nil || name != "fail" || !isPropertyAccessCalleeName(node) { + return false, "" + } + argCount, ok := propertyAccessCallArgCount(node) + if !ok || argCount != 2 { + return false, "" + } + access := node.Parent.AsPropertyAccessExpression() + if access == nil || access.Expression == nil { + return false, "" + } + importName := strings.TrimSpace(sourceSpanText(ctx.SourceFile, access.Expression.Pos(), access.Expression.End())) + if importName == "" { + return false, "" + } + sourceText := ctx.SourceFile.Text() + defaultImport := regexp.MustCompile(`(?m)\bimport\s+` + regexp.QuoteMeta(importName) + `\s+from\s+['"]node:assert['"]`) + namespaceImport := regexp.MustCompile(`(?m)\bimport\s+\*\s+as\s+` + regexp.QuoteMeta(importName) + `\s+from\s+['"]node:assert['"]`) + if !defaultImport.MatchString(sourceText) && !namespaceImport.MatchString(sourceText) { + return false, "" + } + return true, "since v10.0.0 - use fail([message]) or other assert functions instead." +} + +func isDeprecatedFsExistsImport(ctx rule.RuleContext, name string) (bool, string) { + if ctx.SourceFile == nil || name != "exists" { + return false, "" + } + if !nameImportedFromPackage(ctx.SourceFile, name, "fs") { + return false, "" + } + return true, "Since v1.0.0 - Use {@link stat} or {@link access} instead." +} + var NoDeprecatedRule = rule.CreateRule(rule.Rule{ - Name: "no-deprecated", + Name: "no-deprecated", + RequiresTypeInfo: true, Run: func(ctx rule.RuleContext, options any) rule.RuleListeners { if ctx.TypeChecker == nil || ctx.SourceFile == nil { return rule.RuleListeners{} @@ -1704,9 +2013,6 @@ var NoDeprecatedRule = rule.CreateRule(rule.Rule{ if shouldIgnoreDynamicImportDefault(node, diagnostic.Pos(), diagnostic.End(), name, ctx.TypeChecker) { continue } - if node != nil && (node.Kind == ast.KindIdentifier || node.Kind == ast.KindPrivateIdentifier || node.Kind == ast.KindSuperKeyword) { - continue - } symbol := symbolAtLocation(ctx.TypeChecker, node) if isWithinJsxClosingElement(node, diagnostic.Pos(), diagnostic.End()) { continue @@ -1774,7 +2080,41 @@ var NoDeprecatedRule = rule.CreateRule(rule.Rule{ if shouldIgnoreDynamicImportDefault(node, node.Pos(), node.End(), name, ctx.TypeChecker) { return } + reportName := name isDeprecated, reason := getDeprecationReason(ctx, node) + if !isDeprecated && shouldUseDeprecatedVariableSourceFallback(node) { + isDeprecated, reason = deprecatedVariableInfoByNameInSource(ctx, name) + } + if !isDeprecated && isPropertyAccessName(node) { + isDeprecated, reason = deprecatedStructuralPropertyInfoByNameInSource(ctx, name) + if !isDeprecated { + argCount, matchArgCount := propertyAccessCallArgCount(node) + isDeprecated, reason = deprecatedMethodInfoByNameInSource(ctx, name, argCount, matchArgCount) + if isDeprecated && isPropertyAccessCalleeName(node) { + reportName = propertyAccessDisplayName(ctx.SourceFile, node) + } + } + } + if !isDeprecated && isBindingElementNameOrProperty(node) { + isDeprecated, reason = deprecatedPropertyInfoByNameInSource(ctx, name) + } + if !isDeprecated { + argCount, matchArgCount := callArgCountForIdentifierCallee(node) + if matchArgCount { + isDeprecated, reason = deprecatedFunctionInfoByNameInSource(ctx, name, argCount, true) + } else if isIdentifierTaggedTemplateTag(node) { + isDeprecated, reason = deprecatedFunctionInfoByNameInSource(ctx, name, 0, false) + } + } + if !isDeprecated { + isDeprecated, reason = isDeprecatedNodeAssertFailOverload(ctx, node, name) + if isDeprecated { + reportName = propertyAccessDisplayName(ctx.SourceFile, node) + } + } + if !isDeprecated { + isDeprecated, reason = isDeprecatedFsExistsImport(ctx, name) + } if !isDeprecated { return } @@ -1790,23 +2130,23 @@ var NoDeprecatedRule = rule.CreateRule(rule.Rule{ if shouldAllowDiagnostic(allowEntries, name, symbol, ctx.SourceFile) { return } - message := buildDeprecatedMessage(name) + message := buildDeprecatedMessage(reportName) if reason != "" { - message = buildDeprecatedWithReasonMessage(name, reason) + message = buildDeprecatedWithReasonMessage(reportName, reason) } else if symbol != nil { for _, declaration := range symbol.Declarations { if declaration == nil { continue } if reasonFromDecl := deprecatedReasonFromDeclaration(declaration); reasonFromDecl != "" { - message = buildDeprecatedWithReasonMessage(name, reasonFromDecl) + message = buildDeprecatedWithReasonMessage(reportName, reasonFromDecl) break } } } if message.Id != "deprecatedWithReason" { if reasonByName := deprecatedReasonByNameInSource(ctx, name); reasonByName != "" { - message = buildDeprecatedWithReasonMessage(name, reasonByName) + message = buildDeprecatedWithReasonMessage(reportName, reasonByName) } } trimmedRange := utils.TrimNodeTextRange(ctx.SourceFile, node) @@ -1902,23 +2242,30 @@ var NoDeprecatedRule = rule.CreateRule(rule.Rule{ return } objectType := utils.GetConstrainedTypeAtLocation(ctx.TypeChecker, elementAccess.Expression) - if objectType == nil { - return + var propertySymbol *ast.Symbol + if objectType != nil { + propertySymbol = checker.Checker_getPropertyOfType(ctx.TypeChecker, objectType, propertyName) } - propertySymbol := checker.Checker_getPropertyOfType(ctx.TypeChecker, objectType, propertyName) - if !symbolIsDeprecated(ctx.TypeChecker, propertySymbol) { + isDeprecated := symbolIsDeprecated(ctx.TypeChecker, propertySymbol) + sourceDeprecated, sourceReason := deprecatedStructuralPropertyInfoByNameInSource(ctx, propertyName) + if !isDeprecated && !sourceDeprecated { return } if shouldAllowDiagnostic(allowEntries, propertyName, propertySymbol, ctx.SourceFile) { return } message := buildDeprecatedMessage(propertyName) - for _, declaration := range propertySymbol.Declarations { - if reason := deprecatedReasonFromDeclaration(declaration); reason != "" { - message = buildDeprecatedWithReasonMessage(propertyName, reason) - break + if propertySymbol != nil { + for _, declaration := range propertySymbol.Declarations { + if reason := deprecatedReasonFromDeclaration(declaration); reason != "" { + message = buildDeprecatedWithReasonMessage(propertyName, reason) + break + } } } + if message.Id != "deprecatedWithReason" && sourceReason != "" { + message = buildDeprecatedWithReasonMessage(propertyName, sourceReason) + } trimmedRange := utils.TrimNodeTextRange(ctx.SourceFile, elementAccess.ArgumentExpression) reportRange(core.NewTextRange(trimmedRange.Pos(), trimmedRange.End()), message) }, diff --git a/internal/plugins/typescript/rules/no_deprecated/no_deprecated_test.go b/internal/plugins/typescript/rules/no_deprecated/no_deprecated_test.go index bf6ab4713..4ea930b24 100644 --- a/internal/plugins/typescript/rules/no_deprecated/no_deprecated_test.go +++ b/internal/plugins/typescript/rules/no_deprecated/no_deprecated_test.go @@ -113,6 +113,12 @@ oldValue; {MessageId: "deprecated", Line: 1, Column: 40}, }, }, + { + Code: `/** @deprecated */ let oldValue = undefined; oldValue;`, + Errors: []rule_tester.InvalidTestCaseError{ + {MessageId: "deprecated", Line: 1, Column: 46}, + }, + }, { Code: ` /** @deprecated Use newValue instead. */ @@ -245,3 +251,19 @@ oldValue; } }) } + +func TestNoDeprecatedReportsLetInitializedToUndefinedInTsxFixture(t *testing.T) { + t.Parallel() + diagnostics := runNoDeprecatedDiagnosticsForFiles(t, map[string]string{ + "react.tsx": ` + /** @deprecated */ let a = undefined; + a; + `, + }, "react.tsx", nil) + if len(diagnostics) != 1 { + t.Fatalf("expected 1 diagnostic, got %d", len(diagnostics)) + } + if diagnostics[0].Message.Id != "deprecated" { + t.Fatalf("expected message id deprecated, got %s", diagnostics[0].Message.Id) + } +}