Skip to content
Draft
32 changes: 32 additions & 0 deletions internal/fourslash/tests/manual/renameStandardLibrary_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package fourslash_test

import (
"testing"

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

func TestRenameStandardLibrary(t *testing.T) {
t.Parallel()

defer testutil.RecoverAndFail(t, "Panic on fourslash test")
const content = `
// Test that standard library symbols cannot be renamed
/*1*/setTimeout(() => {}, 100);
/*2*/console.log("test");
const arr = [1, 2, 3];
arr./*3*/push(4);
const str = "test";
str./*4*/substring(0, 1);
`
f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content)
defer done()

// Try to rename standard library functions - should fail
markers := []string{"1", "2", "3", "4"}
for _, marker := range markers {
f.GoToMarker(t, marker)
f.VerifyRenameFailed(t, nil /*preferences*/)
}
}
47 changes: 46 additions & 1 deletion internal/ls/findallreferences.go
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,17 @@ func (l *LanguageService) ProvideSymbolsAndEntries(ctx context.Context, uri lspr
return node, nil, false
}

// For rename operations, check if we're trying to rename a standard library symbol
if isRename {
checker, done := program.GetTypeChecker(ctx)
symbol := checker.GetSymbolAtLocation(core.IfElse(node.Kind == ast.KindConstructor && node.Parent.Name() != nil, node.Parent.Name(), node))
done()
if symbol != nil && isDefinedInLibraryFile(program, symbol) {
// Disallow rename for elements that are defined in the standard TypeScript library
return node, nil, false
}
}

var options refOptions
if !isRename {
options.use = referenceUseReferences
Expand All @@ -594,7 +605,14 @@ func (l *LanguageService) ProvideSymbolsAndEntries(ctx context.Context, uri lspr
options.useAliasesForRename = true
}

return node, l.getReferencedSymbolsForNode(ctx, position, node, program, program.GetSourceFiles(), options, nil), true
symbolsAndEntries := l.getReferencedSymbolsForNode(ctx, position, node, program, program.GetSourceFiles(), options, nil)

// When renaming, if symbolsAndEntries is nil (e.g., from blocking a library symbol rename), return false
if isRename && symbolsAndEntries == nil {
return node, nil, false
}

return node, symbolsAndEntries, true
}

func (l *LanguageService) ProvideReferencesFromSymbolAndEntries(ctx context.Context, params *lsproto.ReferenceParams, originalNode *ast.Node, symbolsAndEntries []*SymbolAndEntries) (lsproto.ReferencesResponse, error) {
Expand Down Expand Up @@ -655,10 +673,16 @@ func (l *LanguageService) getImplementationReferenceEntries(ctx context.Context,
}

func (l *LanguageService) ProvideRenameFromSymbolAndEntries(ctx context.Context, params *lsproto.RenameParams, originalNode *ast.Node, symbolsAndEntries []*SymbolAndEntries) (lsproto.WorkspaceEditOrNull, error) {
// Early return if the node is not an identifier (can't be renamed)
if originalNode.Kind != ast.KindIdentifier {
return lsproto.WorkspaceEditOrNull{}, nil
}

// Early return if symbolsAndEntries is nil (e.g., rename was blocked for a standard library symbol)
if symbolsAndEntries == nil {
return lsproto.WorkspaceEditOrNull{}, nil
}

program := l.GetProgram()
entries := core.FlatMap(symbolsAndEntries, func(s *SymbolAndEntries) []*ReferenceEntry { return s.references })
changes := make(map[lsproto.DocumentUri][]*lsproto.TextEdit)
Expand Down Expand Up @@ -944,6 +968,27 @@ func (l *LanguageService) getReferencedSymbolsForNode(ctx context.Context, posit
return l.mergeReferences(program, moduleReferences, references, moduleReferencesOfExportTarget)
}

// isDefinedInLibraryFile checks if a symbol is defined ONLY in the standard TypeScript library.
// Returns true only if ALL declarations are in library files.
func isDefinedInLibraryFile(program *compiler.Program, symbol *ast.Symbol) bool {
if symbol.Declarations == nil || len(symbol.Declarations) == 0 {
return false
}
// Check if ALL declarations are in library files
for _, declaration := range symbol.Declarations {
sourceFile := ast.GetSourceFileOfNode(declaration)
if sourceFile == nil {
return false
}
if !program.IsSourceFileDefaultLibrary(sourceFile.Path()) || !tspath.FileExtensionIs(sourceFile.FileName(), tspath.ExtensionDts) {
// Found a non-library declaration, so it's not purely a library symbol
return false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we elsewhere block the declarations that are from a default library file from being renamed?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way Strada did this was:

    // Disallow rename for elements that are defined in the standard TypeScript library.
    if (declarations.some(declaration => isDefinedInLibraryFile(program, declaration))) {
        return getRenameInfoError(Diagnostics.You_cannot_rename_elements_that_are_defined_in_the_standard_TypeScript_library);
    }

function isDefinedInLibraryFile(program: Program, declaration: Node) {
    const sourceFile = declaration.getSourceFile();
    return program.isSourceFileDefaultLibrary(sourceFile) && fileExtensionIs(sourceFile.fileName, Extension.Dts);
}

This is all of the code that was used to prevent renames, as far as I can tell (beside the ones that are supposed to prevent renaming keywords.

@copilot I don't think what you've written is exactly equivalent; please refactor this to look more like the original code, e.g. not inverted, using core.Some, etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored to match the TypeScript implementation exactly. Changed to use core.Some to check if any declaration is in a library file, and updated isDefinedInLibraryFile to take a declaration parameter instead of a symbol. Commit: f6734f9

}
}
// All declarations are in library files
return true
}

func (l *LanguageService) getReferencedSymbolsForModuleIfDeclaredBySourceFile(ctx context.Context, symbol *ast.Symbol, program *compiler.Program, sourceFiles []*ast.SourceFile, checker *checker.Checker, options refOptions, sourceFilesSet *collections.Set[string]) []*SymbolAndEntries {
moduleSourceFileName := ""
if symbol == nil || !((symbol.Flags&ast.SymbolFlagsModule != 0) && len(symbol.Declarations) != 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,11 @@
// const external = true;
//
// function f({
// lvl1 = /*RENAME*/[|externalRENAME|],
// nested: { lvl2 = [|externalRENAME|]},
// oldName: newName = [|externalRENAME|]
// lvl1 = /*RENAME*/external,
// nested: { lvl2 = external},
// oldName: newName = external
// }) {}
//
// const {
// lvl1 = [|externalRENAME|],
// nested: { lvl2 = [|externalRENAME|]},
// oldName: newName = [|externalRENAME|]
// } = obj;

// === bundled:///libs/lib.dom.d.ts ===
// --- (line: --) skipped ---
// *
// * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/external)
// */
// declare var [|externalRENAME|]: External;
// /**
// * The **`Window.frameElement`** property returns the element (such as iframe or object) in which the window is embedded.
// *
// --- (line: --) skipped ---
//
// --- (line: 8) skipped ---



Expand All @@ -43,28 +26,12 @@
// const external = true;
//
// function f({
// lvl1 = [|externalRENAME|],
// nested: { lvl2 = /*RENAME*/[|externalRENAME|]},
// oldName: newName = [|externalRENAME|]
// lvl1 = external,
// nested: { lvl2 = /*RENAME*/external},
// oldName: newName = external
// }) {}
//
// const {
// lvl1 = [|externalRENAME|],
// nested: { lvl2 = [|externalRENAME|]},
// oldName: newName = [|externalRENAME|]
// } = obj;

// === bundled:///libs/lib.dom.d.ts ===
// --- (line: --) skipped ---
// *
// * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/external)
// */
// declare var [|externalRENAME|]: External;
// /**
// * The **`Window.frameElement`** property returns the element (such as iframe or object) in which the window is embedded.
// *
// --- (line: --) skipped ---
//
// --- (line: 9) skipped ---



Expand All @@ -73,115 +40,46 @@
// const external = true;
//
// function f({
// lvl1 = [|externalRENAME|],
// nested: { lvl2 = [|externalRENAME|]},
// oldName: newName = /*RENAME*/[|externalRENAME|]
// lvl1 = external,
// nested: { lvl2 = external},
// oldName: newName = /*RENAME*/external
// }) {}
//
// const {
// lvl1 = [|externalRENAME|],
// nested: { lvl2 = [|externalRENAME|]},
// oldName: newName = [|externalRENAME|]
// } = obj;

// === bundled:///libs/lib.dom.d.ts ===
// --- (line: --) skipped ---
// *
// * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/external)
// */
// declare var [|externalRENAME|]: External;
// /**
// * The **`Window.frameElement`** property returns the element (such as iframe or object) in which the window is embedded.
// *
// --- (line: --) skipped ---
//
// --- (line: 10) skipped ---



// === findRenameLocations ===
// === /renameBindingElementInitializerExternal.ts ===
// const external = true;
//
// function f({
// lvl1 = [|externalRENAME|],
// nested: { lvl2 = [|externalRENAME|]},
// oldName: newName = [|externalRENAME|]
// --- (line: 6) skipped ---
// }) {}
//
// const {
// lvl1 = /*RENAME*/[|externalRENAME|],
// nested: { lvl2 = [|externalRENAME|]},
// oldName: newName = [|externalRENAME|]
// lvl1 = /*RENAME*/external,
// nested: { lvl2 = external},
// oldName: newName = external
// } = obj;

// === bundled:///libs/lib.dom.d.ts ===
// --- (line: --) skipped ---
// *
// * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/external)
// */
// declare var [|externalRENAME|]: External;
// /**
// * The **`Window.frameElement`** property returns the element (such as iframe or object) in which the window is embedded.
// *
// --- (line: --) skipped ---
//



// === findRenameLocations ===
// === /renameBindingElementInitializerExternal.ts ===
// const external = true;
//
// function f({
// lvl1 = [|externalRENAME|],
// nested: { lvl2 = [|externalRENAME|]},
// oldName: newName = [|externalRENAME|]
// }) {}
// --- (line: 7) skipped ---
//
// const {
// lvl1 = [|externalRENAME|],
// nested: { lvl2 = /*RENAME*/[|externalRENAME|]},
// oldName: newName = [|externalRENAME|]
// lvl1 = external,
// nested: { lvl2 = /*RENAME*/external},
// oldName: newName = external
// } = obj;

// === bundled:///libs/lib.dom.d.ts ===
// --- (line: --) skipped ---
// *
// * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/external)
// */
// declare var [|externalRENAME|]: External;
// /**
// * The **`Window.frameElement`** property returns the element (such as iframe or object) in which the window is embedded.
// *
// --- (line: --) skipped ---
//



// === findRenameLocations ===
// === /renameBindingElementInitializerExternal.ts ===
// const external = true;
//
// function f({
// lvl1 = [|externalRENAME|],
// nested: { lvl2 = [|externalRENAME|]},
// oldName: newName = [|externalRENAME|]
// }) {}
//
// --- (line: 8) skipped ---
// const {
// lvl1 = [|externalRENAME|],
// nested: { lvl2 = [|externalRENAME|]},
// oldName: newName = /*RENAME*/[|externalRENAME|]
// } = obj;

// === bundled:///libs/lib.dom.d.ts ===
// --- (line: --) skipped ---
// *
// * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/external)
// */
// declare var [|externalRENAME|]: External;
// /**
// * The **`Window.frameElement`** property returns the element (such as iframe or object) in which the window is embedded.
// *
// --- (line: --) skipped ---
//
// lvl1 = external,
// nested: { lvl2 = external},
// oldName: newName = /*RENAME*/external
// } = obj;
Loading