From 7bd7dfc007999f6ed2ab0491821d9d136b6293d4 Mon Sep 17 00:00:00 2001 From: navya9singh <108360753+navya9singh@users.noreply.github.com> Date: Mon, 1 Jul 2024 15:00:29 -0700 Subject: [PATCH] Paste without imports (#59093) --- src/services/pasteEdits.ts | 2 +- src/services/refactors/moveToFile.ts | 15 +- .../pasteEdits_noImportNeeded.js | 310 ++++++++++++++++++ .../server/pasteEdits_noImportNeeded.ts | 25 ++ 4 files changed, 346 insertions(+), 6 deletions(-) create mode 100644 tests/baselines/reference/tsserver/fourslashServer/pasteEdits_noImportNeeded.js create mode 100644 tests/cases/fourslash/server/pasteEdits_noImportNeeded.ts diff --git a/src/services/pasteEdits.ts b/src/services/pasteEdits.ts index 9b67d62e965..2aad38e571a 100644 --- a/src/services/pasteEdits.ts +++ b/src/services/pasteEdits.ts @@ -90,7 +90,7 @@ function pasteEdits( } statements.push(...statementsInSourceFile.slice(startNodeIndex, endNodeIndex === -1 ? statementsInSourceFile.length : endNodeIndex + 1)); }); - const usage = getUsageInfo(copiedFrom.file, statements, originalProgram!.getTypeChecker(), getExistingLocals(updatedFile, statements, originalProgram!.getTypeChecker())); + const usage = getUsageInfo(copiedFrom.file, statements, originalProgram!.getTypeChecker(), getExistingLocals(updatedFile, statements, originalProgram!.getTypeChecker()), { pos: copiedFrom.range[0].pos, end: copiedFrom.range[copiedFrom.range.length - 1].end }); Debug.assertIsDefined(originalProgram); const useEsModuleSyntax = !fileShouldUseJavaScriptRequire(targetFile.fileName, originalProgram, host, !!copiedFrom.file.commonJsModuleIndicator); addExportsInOldFile(copiedFrom.file, usage.targetFileImportsFromOldFile, changes, useEsModuleSyntax); diff --git a/src/services/refactors/moveToFile.ts b/src/services/refactors/moveToFile.ts index 7f755cff12c..da6d806a06f 100644 --- a/src/services/refactors/moveToFile.ts +++ b/src/services/refactors/moveToFile.ts @@ -129,6 +129,7 @@ import { PropertyAccessExpression, PropertyAssignment, QuotePreference, + rangeContainsRange, RefactorContext, RefactorEditInfo, RequireOrImportCall, @@ -145,6 +146,7 @@ import { SyntaxKind, takeWhile, textChanges, + TextRange, TransformFlags, tryCast, TypeAliasDeclaration, @@ -861,7 +863,7 @@ function isPureImport(node: Node): boolean { } /** @internal */ -export function getUsageInfo(oldFile: SourceFile, toMove: readonly Statement[], checker: TypeChecker, existingTargetLocals: ReadonlySet = new Set()): UsageInfo { +export function getUsageInfo(oldFile: SourceFile, toMove: readonly Statement[], checker: TypeChecker, existingTargetLocals: ReadonlySet = new Set(), enclosingRange?: TextRange): UsageInfo { const movedSymbols = new Set(); const oldImportsNeededByTargetFile = new Map(); const targetFileImportsFromOldFile = new Map(); @@ -880,7 +882,7 @@ export function getUsageInfo(oldFile: SourceFile, toMove: readonly Statement[], const unusedImportsFromOldFile = new Set(); for (const statement of toMove) { - forEachReference(statement, checker, (symbol, isValidTypeOnlyUseSite) => { + forEachReference(statement, checker, enclosingRange, (symbol, isValidTypeOnlyUseSite) => { if (!symbol.declarations || isGlobalType(checker, symbol)) { return; } @@ -916,7 +918,7 @@ export function getUsageInfo(oldFile: SourceFile, toMove: readonly Statement[], unusedImportsFromOldFile.delete(jsxNamespaceSymbol); } - forEachReference(statement, checker, (symbol, isValidTypeOnlyUseSite) => { + forEachReference(statement, checker, enclosingRange, (symbol, isValidTypeOnlyUseSite) => { if (movedSymbols.has(symbol)) oldFileImportsFromTargetFile.set(symbol, isValidTypeOnlyUseSite); unusedImportsFromOldFile.delete(symbol); }); @@ -959,9 +961,12 @@ function inferNewFileName(importsFromNewFile: Map, movedSymbols return forEachKey(importsFromNewFile, symbolNameNoDefault) || forEachKey(movedSymbols, symbolNameNoDefault) || "newFile"; } -function forEachReference(node: Node, checker: TypeChecker, onReference: (s: Symbol, isValidTypeOnlyUseSite: boolean) => void) { +function forEachReference(node: Node, checker: TypeChecker, enclosingRange: TextRange | undefined, onReference: (s: Symbol, isValidTypeOnlyUseSite: boolean) => void) { node.forEachChild(function cb(node) { if (isIdentifier(node) && !isDeclarationName(node)) { + if (enclosingRange && !rangeContainsRange(enclosingRange, node)) { + return; + } const sym = checker.getSymbolAtLocation(node); if (sym) onReference(sym, isValidTypeOnlyAliasUseSite(node)); } @@ -1140,7 +1145,7 @@ export function getExistingLocals(sourceFile: SourceFile, statements: readonly S } for (const statement of statements) { - forEachReference(statement, checker, s => { + forEachReference(statement, checker, /*enclosingRange*/ undefined, s => { const symbol = skipAlias(s, checker); if (symbol.valueDeclaration && getSourceFileOfNode(symbol.valueDeclaration).path === sourceFile.path) { existingLocals.add(symbol); diff --git a/tests/baselines/reference/tsserver/fourslashServer/pasteEdits_noImportNeeded.js b/tests/baselines/reference/tsserver/fourslashServer/pasteEdits_noImportNeeded.js new file mode 100644 index 00000000000..9ef264793ba --- /dev/null +++ b/tests/baselines/reference/tsserver/fourslashServer/pasteEdits_noImportNeeded.js @@ -0,0 +1,310 @@ +currentDirectory:: / useCaseSensitiveFileNames: false +Info seq [hh:mm:ss:mss] Provided types map file "/typesMap.json" doesn't exist +//// [/a.ts] +export interface Foo { } + +export const foo: Foo = {} + +//// [/b.ts] + + +//// [/lib.d.ts] +lib.d.ts-Text + +//// [/lib.decorators.d.ts] +lib.decorators.d.ts-Text + +//// [/lib.decorators.legacy.d.ts] +lib.decorators.legacy.d.ts-Text + +//// [/tsconfig.json] +{ "files": ["a.ts", "b.ts"] } + + +Info seq [hh:mm:ss:mss] request: + { + "seq": 0, + "type": "request", + "arguments": { + "file": "/b.ts" + }, + "command": "open" + } +Info seq [hh:mm:ss:mss] getConfigFileNameForFile:: File: /b.ts ProjectRootPath: undefined:: Result: /tsconfig.json +Info seq [hh:mm:ss:mss] Creating configuration project /tsconfig.json +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /tsconfig.json 2000 undefined Project: /tsconfig.json WatchType: Config file +Info seq [hh:mm:ss:mss] event: + { + "seq": 0, + "type": "event", + "event": "projectLoadingStart", + "body": { + "projectName": "/tsconfig.json", + "reason": "Creating possible configured project for /b.ts to open" + } + } +Info seq [hh:mm:ss:mss] Config: /tsconfig.json : { + "rootNames": [ + "/a.ts", + "/b.ts" + ], + "options": { + "configFilePath": "/tsconfig.json" + } +} +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /a.ts 500 undefined WatchType: Closed Script info +Info seq [hh:mm:ss:mss] Starting updateGraphWorker: Project: /tsconfig.json +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /lib.d.ts 500 undefined WatchType: Closed Script info +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /lib.decorators.d.ts 500 undefined WatchType: Closed Script info +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /lib.decorators.legacy.d.ts 500 undefined WatchType: Closed Script info +Info seq [hh:mm:ss:mss] Finishing updateGraphWorker: Project: /tsconfig.json projectStateVersion: 1 projectProgramVersion: 0 structureChanged: true structureIsReused:: Not Elapsed:: *ms +Info seq [hh:mm:ss:mss] Project '/tsconfig.json' (Configured) +Info seq [hh:mm:ss:mss] Files (5) + /lib.d.ts Text-1 lib.d.ts-Text + /lib.decorators.d.ts Text-1 lib.decorators.d.ts-Text + /lib.decorators.legacy.d.ts Text-1 lib.decorators.legacy.d.ts-Text + /a.ts Text-1 "export interface Foo { }\n\nexport const foo: Foo = {}" + /b.ts SVC-1-0 "" + + + lib.d.ts + Default library for target 'es5' + lib.decorators.d.ts + Library referenced via 'decorators' from file 'lib.d.ts' + lib.decorators.legacy.d.ts + Library referenced via 'decorators.legacy' from file 'lib.d.ts' + a.ts + Part of 'files' list in tsconfig.json + b.ts + Part of 'files' list in tsconfig.json + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] event: + { + "seq": 0, + "type": "event", + "event": "projectLoadingFinish", + "body": { + "projectName": "/tsconfig.json" + } + } +Info seq [hh:mm:ss:mss] event: + { + "seq": 0, + "type": "event", + "event": "configFileDiag", + "body": { + "triggerFile": "/b.ts", + "configFile": "/tsconfig.json", + "diagnostics": [] + } + } +Info seq [hh:mm:ss:mss] Project '/tsconfig.json' (Configured) +Info seq [hh:mm:ss:mss] Files (5) + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] Open files: +Info seq [hh:mm:ss:mss] FileName: /b.ts ProjectRootPath: undefined +Info seq [hh:mm:ss:mss] Projects: /tsconfig.json +Info seq [hh:mm:ss:mss] response: + { + "seq": 0, + "type": "response", + "command": "open", + "request_seq": 0, + "success": true, + "performanceData": { + "updateGraphDurationMs": * + } + } +After Request +watchedFiles:: +/a.ts: *new* + {"pollingInterval":500} +/lib.d.ts: *new* + {"pollingInterval":500} +/lib.decorators.d.ts: *new* + {"pollingInterval":500} +/lib.decorators.legacy.d.ts: *new* + {"pollingInterval":500} +/tsconfig.json: *new* + {"pollingInterval":2000} + +Projects:: +/tsconfig.json (Configured) *new* + projectStateVersion: 1 + projectProgramVersion: 1 + +ScriptInfos:: +/a.ts *new* + version: Text-1 + containingProjects: 1 + /tsconfig.json +/b.ts (Open) *new* + version: SVC-1-0 + containingProjects: 1 + /tsconfig.json *default* +/lib.d.ts *new* + version: Text-1 + containingProjects: 1 + /tsconfig.json +/lib.decorators.d.ts *new* + version: Text-1 + containingProjects: 1 + /tsconfig.json +/lib.decorators.legacy.d.ts *new* + version: Text-1 + containingProjects: 1 + /tsconfig.json + +Info seq [hh:mm:ss:mss] request: + { + "seq": 1, + "type": "request", + "arguments": { + "formatOptions": { + "indentSize": 4, + "tabSize": 4, + "newLineCharacter": "\n", + "convertTabsToSpaces": true, + "indentStyle": 2, + "insertSpaceAfterConstructor": false, + "insertSpaceAfterCommaDelimiter": true, + "insertSpaceAfterSemicolonInForStatements": true, + "insertSpaceBeforeAndAfterBinaryOperators": true, + "insertSpaceAfterKeywordsInControlFlowStatements": true, + "insertSpaceAfterFunctionKeywordForAnonymousFunctions": false, + "insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis": false, + "insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets": false, + "insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": true, + "insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces": false, + "insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces": false, + "insertSpaceBeforeFunctionParenthesis": false, + "placeOpenBraceOnNewLineForFunctions": false, + "placeOpenBraceOnNewLineForControlBlocks": false, + "semicolons": "ignore", + "trimTrailingWhitespace": true, + "indentSwitchCase": true + } + }, + "command": "configure" + } +Info seq [hh:mm:ss:mss] Format host information updated +Info seq [hh:mm:ss:mss] response: + { + "seq": 0, + "type": "response", + "command": "configure", + "request_seq": 1, + "success": true + } +Info seq [hh:mm:ss:mss] request: + { + "seq": 2, + "type": "request", + "arguments": { + "file": "/b.ts", + "pastedText": [ + "export" + ], + "pasteLocations": [ + { + "start": { + "line": 1, + "offset": 1 + }, + "end": { + "line": 1, + "offset": 1 + } + } + ], + "copiedFrom": { + "file": "a.ts", + "spans": [ + { + "start": { + "line": 3, + "offset": 1 + }, + "end": { + "line": 3, + "offset": 7 + } + } + ] + } + }, + "command": "getPasteEdits" + } +Info seq [hh:mm:ss:mss] Starting updateGraphWorker: Project: /tsconfig.json +Info seq [hh:mm:ss:mss] Finishing updateGraphWorker: Project: /tsconfig.json projectStateVersion: 2 projectProgramVersion: 1 structureChanged: false structureIsReused:: Completely Elapsed:: *ms +Info seq [hh:mm:ss:mss] Project '/tsconfig.json' (Configured) +Info seq [hh:mm:ss:mss] Files (5) + /lib.d.ts Text-1 lib.d.ts-Text + /lib.decorators.d.ts Text-1 lib.decorators.d.ts-Text + /lib.decorators.legacy.d.ts Text-1 lib.decorators.legacy.d.ts-Text + /a.ts Text-1 "export interface Foo { }\n\nexport const foo: Foo = {}" + /b.ts SVC-1-1 "export" + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] response: + { + "seq": 0, + "type": "response", + "command": "getPasteEdits", + "request_seq": 2, + "success": true, + "performanceData": { + "updateGraphDurationMs": * + }, + "body": { + "edits": [ + { + "fileName": "/b.ts", + "textChanges": [ + { + "start": { + "line": 1, + "offset": 1 + }, + "end": { + "line": 1, + "offset": 1 + }, + "newText": "export" + } + ] + } + ], + "fixId": "providePostPasteEdits" + } + } +After Request +Projects:: +/tsconfig.json (Configured) *changed* + projectStateVersion: 3 *changed* + projectProgramVersion: 1 + dirty: true *changed* + +ScriptInfos:: +/a.ts + version: Text-1 + containingProjects: 1 + /tsconfig.json +/b.ts (Open) *changed* + version: SVC-1-2 *changed* + containingProjects: 1 + /tsconfig.json *default* +/lib.d.ts + version: Text-1 + containingProjects: 1 + /tsconfig.json +/lib.decorators.d.ts + version: Text-1 + containingProjects: 1 + /tsconfig.json +/lib.decorators.legacy.d.ts + version: Text-1 + containingProjects: 1 + /tsconfig.json diff --git a/tests/cases/fourslash/server/pasteEdits_noImportNeeded.ts b/tests/cases/fourslash/server/pasteEdits_noImportNeeded.ts new file mode 100644 index 00000000000..c23f3d69f30 --- /dev/null +++ b/tests/cases/fourslash/server/pasteEdits_noImportNeeded.ts @@ -0,0 +1,25 @@ +/// + +// @Filename: /b.ts +////[||] + +// @Filename: /a.ts +//// export interface Foo { } +//// +//// [|export|] const foo: Foo = {} + +// @Filename: /tsconfig.json +////{ "files": ["a.ts", "b.ts"] } + +const range = test.ranges(); +verify.pasteEdits({ + args: { + pastedText: [`export`], + pasteLocations: [range[0]], + copiedFrom: { file: "a.ts", range: [range[1]] }, + }, + newFileContents: { + "/b.ts": +`export` + } +});