Skip to content

Commit 2161893

Browse files
authored
fix paste edits range: include all completely selected identifiers (#60339)
1 parent cb44488 commit 2161893

22 files changed

+3969
-6
lines changed

src/services/pasteEdits.ts

+27-4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
fileShouldUseJavaScriptRequire,
66
findAncestor,
77
findIndex,
8+
findTokenOnLeftOfPosition,
89
forEachChild,
910
formatting,
1011
getNewLineOrDefaultFromHost,
@@ -50,11 +51,16 @@ export function pasteEditsProvider(
5051
return { edits: changes, fixId };
5152
}
5253

54+
interface CopiedFromInfo {
55+
file: SourceFile;
56+
range: TextRange[];
57+
}
58+
5359
function pasteEdits(
5460
targetFile: SourceFile,
5561
pastedText: string[],
5662
pasteLocations: TextRange[],
57-
copiedFrom: { file: SourceFile; range: TextRange[]; } | undefined,
63+
copiedFrom: CopiedFromInfo | undefined,
5864
host: LanguageServiceHost,
5965
preferences: UserPreferences,
6066
formatContext: formatting.FormatContext,
@@ -93,11 +99,13 @@ function pasteEdits(
9399
}
94100
statements.push(...statementsInSourceFile.slice(startNodeIndex, endNodeIndex === -1 ? statementsInSourceFile.length : endNodeIndex + 1));
95101
});
96-
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 });
97-
Debug.assertIsDefined(originalProgram);
102+
Debug.assertIsDefined(originalProgram, "no original program found");
103+
const originalProgramTypeChecker = originalProgram.getTypeChecker();
104+
const usageInfoRange = getUsageInfoRangeForPasteEdits(copiedFrom);
105+
const usage = getUsageInfo(copiedFrom.file, statements, originalProgramTypeChecker, getExistingLocals(updatedFile, statements, originalProgramTypeChecker), usageInfoRange);
98106
const useEsModuleSyntax = !fileShouldUseJavaScriptRequire(targetFile.fileName, originalProgram, host, !!copiedFrom.file.commonJsModuleIndicator);
99107
addExportsInOldFile(copiedFrom.file, usage.targetFileImportsFromOldFile, changes, useEsModuleSyntax);
100-
addTargetFileImports(copiedFrom.file, usage.oldImportsNeededByTargetFile, usage.targetFileImportsFromOldFile, originalProgram.getTypeChecker(), updatedProgram, importAdder);
108+
addTargetFileImports(copiedFrom.file, usage.oldImportsNeededByTargetFile, usage.targetFileImportsFromOldFile, originalProgramTypeChecker, updatedProgram, importAdder);
101109
}
102110
else {
103111
const context: CodeFixContextBase = {
@@ -167,3 +175,18 @@ function pasteEdits(
167175
);
168176
});
169177
}
178+
179+
/**
180+
* Adjusts the range for `getUsageInfo` to correctly include identifiers at the edges of the copied text.
181+
*/
182+
function getUsageInfoRangeForPasteEdits({ file: sourceFile, range }: CopiedFromInfo) {
183+
const pos = range[0].pos;
184+
const end = range[range.length - 1].end;
185+
const startToken = getTokenAtPosition(sourceFile, pos);
186+
const endToken = findTokenOnLeftOfPosition(sourceFile, pos) ?? getTokenAtPosition(sourceFile, end);
187+
// Since the range is only used to check identifiers, we do not need to adjust range when the tokens at the edges are not identifiers.
188+
return {
189+
pos: isIdentifier(startToken) && pos <= startToken.getStart(sourceFile) ? startToken.getFullStart() : pos,
190+
end: isIdentifier(endToken) && end === endToken.getEnd() ? textChanges.getAdjustedEndPosition(sourceFile, endToken, {}) : end,
191+
};
192+
}

src/services/textChanges.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ function getAdjustedRange(sourceFile: SourceFile, startNode: Node, endNode: Node
352352
return { pos: getAdjustedStartPosition(sourceFile, startNode, options), end: getAdjustedEndPosition(sourceFile, endNode, options) };
353353
}
354354

355-
function getAdjustedStartPosition(sourceFile: SourceFile, node: Node, options: ConfigurableStartEnd, hasTrailingComment = false) {
355+
function getAdjustedStartPosition(sourceFile: SourceFile, node: Node, options: ConfigurableStartEnd, hasTrailingComment = false): number {
356356
const { leadingTriviaOption } = options;
357357
if (leadingTriviaOption === LeadingTriviaOption.Exclude) {
358358
return node.getStart(sourceFile);
@@ -436,7 +436,8 @@ function getEndPositionOfMultilineTrailingComment(sourceFile: SourceFile, node:
436436
return undefined;
437437
}
438438

439-
function getAdjustedEndPosition(sourceFile: SourceFile, node: Node, options: ConfigurableEnd): number {
439+
/** @internal */
440+
export function getAdjustedEndPosition(sourceFile: SourceFile, node: Node, options: ConfigurableEnd): number {
440441
const { end } = node;
441442
const { trailingTriviaOption } = options;
442443
if (trailingTriviaOption === TrailingTriviaOption.Exclude) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
Info seq [hh:mm:ss:mss] currentDirectory:: /home/src/Vscode/Projects/bin useCaseSensitiveFileNames:: false
2+
Info seq [hh:mm:ss:mss] libs Location:: /home/src/tslibs/TS/Lib
3+
Info seq [hh:mm:ss:mss] globalTypingsCacheLocation:: /home/src/Library/Caches/typescript
4+
Info seq [hh:mm:ss:mss] Provided types map file "/home/src/tslibs/TS/Lib/typesMap.json" doesn't exist
5+
//// [/home/src/tslibs/TS/Lib/lib.d.ts]
6+
lib.d.ts-Text
7+
8+
//// [/home/src/tslibs/TS/Lib/lib.decorators.d.ts]
9+
lib.decorators.d.ts-Text
10+
11+
//// [/home/src/tslibs/TS/Lib/lib.decorators.legacy.d.ts]
12+
lib.decorators.legacy.d.ts-Text
13+
14+
//// [/home/src/workspaces/project/a.ts]
15+
function foo() { }
16+
const x = foo()
17+
18+
foo()
19+
x
20+
21+
//// [/home/src/workspaces/project/folder/target.ts]
22+
23+
24+
//// [/home/src/workspaces/project/tsconfig.json]
25+
{ "files": ["a.ts", "folder/target.ts"] }
26+
27+
28+
Info seq [hh:mm:ss:mss] request:
29+
{
30+
"seq": 0,
31+
"type": "request",
32+
"arguments": {
33+
"file": "/home/src/workspaces/project/folder/target.ts"
34+
},
35+
"command": "open"
36+
}
37+
Info seq [hh:mm:ss:mss] getConfigFileNameForFile:: File: /home/src/workspaces/project/folder/target.ts ProjectRootPath: undefined:: Result: /home/src/workspaces/project/tsconfig.json
38+
Info seq [hh:mm:ss:mss] Creating ConfiguredProject: /home/src/workspaces/project/tsconfig.json, currentDirectory: /home/src/workspaces/project
39+
Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /home/src/workspaces/project/tsconfig.json 2000 undefined Project: /home/src/workspaces/project/tsconfig.json WatchType: Config file
40+
Info seq [hh:mm:ss:mss] Config: /home/src/workspaces/project/tsconfig.json : {
41+
"rootNames": [
42+
"/home/src/workspaces/project/a.ts",
43+
"/home/src/workspaces/project/folder/target.ts"
44+
],
45+
"options": {
46+
"configFilePath": "/home/src/workspaces/project/tsconfig.json"
47+
}
48+
}
49+
Info seq [hh:mm:ss:mss] event:
50+
{
51+
"seq": 0,
52+
"type": "event",
53+
"event": "projectLoadingStart",
54+
"body": {
55+
"projectName": "/home/src/workspaces/project/tsconfig.json",
56+
"reason": "Creating possible configured project for /home/src/workspaces/project/folder/target.ts to open"
57+
}
58+
}
59+
Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /home/src/workspaces/project/a.ts 500 undefined WatchType: Closed Script info
60+
Info seq [hh:mm:ss:mss] Starting updateGraphWorker: Project: /home/src/workspaces/project/tsconfig.json
61+
Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /home/src/tslibs/TS/Lib/lib.d.ts 500 undefined WatchType: Closed Script info
62+
Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: /home/src/workspaces/project/node_modules 1 undefined Project: /home/src/workspaces/project/tsconfig.json WatchType: Failed Lookup Locations
63+
Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /home/src/workspaces/project/node_modules 1 undefined Project: /home/src/workspaces/project/tsconfig.json WatchType: Failed Lookup Locations
64+
Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: /home/src/workspaces/node_modules 1 undefined Project: /home/src/workspaces/project/tsconfig.json WatchType: Failed Lookup Locations
65+
Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /home/src/workspaces/node_modules 1 undefined Project: /home/src/workspaces/project/tsconfig.json WatchType: Failed Lookup Locations
66+
Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /home/src/tslibs/TS/Lib/lib.decorators.d.ts 500 undefined WatchType: Closed Script info
67+
Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /home/src/tslibs/TS/Lib/lib.decorators.legacy.d.ts 500 undefined WatchType: Closed Script info
68+
Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: /home/src/workspaces/project/node_modules/@types 1 undefined Project: /home/src/workspaces/project/tsconfig.json WatchType: Type roots
69+
Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /home/src/workspaces/project/node_modules/@types 1 undefined Project: /home/src/workspaces/project/tsconfig.json WatchType: Type roots
70+
Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: /home/src/workspaces/node_modules/@types 1 undefined Project: /home/src/workspaces/project/tsconfig.json WatchType: Type roots
71+
Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: /home/src/workspaces/node_modules/@types 1 undefined Project: /home/src/workspaces/project/tsconfig.json WatchType: Type roots
72+
Info seq [hh:mm:ss:mss] Finishing updateGraphWorker: Project: /home/src/workspaces/project/tsconfig.json projectStateVersion: 1 projectProgramVersion: 0 structureChanged: true structureIsReused:: Not Elapsed:: *ms
73+
Info seq [hh:mm:ss:mss] Project '/home/src/workspaces/project/tsconfig.json' (Configured)
74+
Info seq [hh:mm:ss:mss] Files (5)
75+
/home/src/tslibs/TS/Lib/lib.d.ts Text-1 lib.d.ts-Text
76+
/home/src/tslibs/TS/Lib/lib.decorators.d.ts Text-1 lib.decorators.d.ts-Text
77+
/home/src/tslibs/TS/Lib/lib.decorators.legacy.d.ts Text-1 lib.decorators.legacy.d.ts-Text
78+
/home/src/workspaces/project/a.ts Text-1 "function foo() { }\nconst x = foo()\n\nfoo()\nx"
79+
/home/src/workspaces/project/folder/target.ts SVC-1-0 ""
80+
81+
82+
../../tslibs/TS/Lib/lib.d.ts
83+
Default library for target 'es5'
84+
../../tslibs/TS/Lib/lib.decorators.d.ts
85+
Library referenced via 'decorators' from file '../../tslibs/TS/Lib/lib.d.ts'
86+
../../tslibs/TS/Lib/lib.decorators.legacy.d.ts
87+
Library referenced via 'decorators.legacy' from file '../../tslibs/TS/Lib/lib.d.ts'
88+
a.ts
89+
Part of 'files' list in tsconfig.json
90+
folder/target.ts
91+
Part of 'files' list in tsconfig.json
92+
93+
Info seq [hh:mm:ss:mss] -----------------------------------------------
94+
Info seq [hh:mm:ss:mss] event:
95+
{
96+
"seq": 0,
97+
"type": "event",
98+
"event": "projectLoadingFinish",
99+
"body": {
100+
"projectName": "/home/src/workspaces/project/tsconfig.json"
101+
}
102+
}
103+
Info seq [hh:mm:ss:mss] event:
104+
{
105+
"seq": 0,
106+
"type": "event",
107+
"event": "configFileDiag",
108+
"body": {
109+
"triggerFile": "/home/src/workspaces/project/folder/target.ts",
110+
"configFile": "/home/src/workspaces/project/tsconfig.json",
111+
"diagnostics": []
112+
}
113+
}
114+
Info seq [hh:mm:ss:mss] Project '/home/src/workspaces/project/tsconfig.json' (Configured)
115+
Info seq [hh:mm:ss:mss] Files (5)
116+
117+
Info seq [hh:mm:ss:mss] -----------------------------------------------
118+
Info seq [hh:mm:ss:mss] Open files:
119+
Info seq [hh:mm:ss:mss] FileName: /home/src/workspaces/project/folder/target.ts ProjectRootPath: undefined
120+
Info seq [hh:mm:ss:mss] Projects: /home/src/workspaces/project/tsconfig.json
121+
Info seq [hh:mm:ss:mss] response:
122+
{
123+
"seq": 0,
124+
"type": "response",
125+
"command": "open",
126+
"request_seq": 0,
127+
"success": true,
128+
"performanceData": {
129+
"updateGraphDurationMs": *
130+
}
131+
}
132+
After Request
133+
watchedFiles::
134+
/home/src/tslibs/TS/Lib/lib.d.ts: *new*
135+
{"pollingInterval":500}
136+
/home/src/tslibs/TS/Lib/lib.decorators.d.ts: *new*
137+
{"pollingInterval":500}
138+
/home/src/tslibs/TS/Lib/lib.decorators.legacy.d.ts: *new*
139+
{"pollingInterval":500}
140+
/home/src/workspaces/project/a.ts: *new*
141+
{"pollingInterval":500}
142+
/home/src/workspaces/project/tsconfig.json: *new*
143+
{"pollingInterval":2000}
144+
145+
watchedDirectoriesRecursive::
146+
/home/src/workspaces/node_modules: *new*
147+
{}
148+
/home/src/workspaces/node_modules/@types: *new*
149+
{}
150+
/home/src/workspaces/project/node_modules: *new*
151+
{}
152+
/home/src/workspaces/project/node_modules/@types: *new*
153+
{}
154+
155+
Projects::
156+
/home/src/workspaces/project/tsconfig.json (Configured) *new*
157+
projectStateVersion: 1
158+
projectProgramVersion: 1
159+
autoImportProviderHost: false
160+
161+
ScriptInfos::
162+
/home/src/tslibs/TS/Lib/lib.d.ts *new*
163+
version: Text-1
164+
containingProjects: 1
165+
/home/src/workspaces/project/tsconfig.json
166+
/home/src/tslibs/TS/Lib/lib.decorators.d.ts *new*
167+
version: Text-1
168+
containingProjects: 1
169+
/home/src/workspaces/project/tsconfig.json
170+
/home/src/tslibs/TS/Lib/lib.decorators.legacy.d.ts *new*
171+
version: Text-1
172+
containingProjects: 1
173+
/home/src/workspaces/project/tsconfig.json
174+
/home/src/workspaces/project/a.ts *new*
175+
version: Text-1
176+
containingProjects: 1
177+
/home/src/workspaces/project/tsconfig.json
178+
/home/src/workspaces/project/folder/target.ts (Open) *new*
179+
version: SVC-1-0
180+
containingProjects: 1
181+
/home/src/workspaces/project/tsconfig.json *default*
182+
183+
Info seq [hh:mm:ss:mss] request:
184+
{
185+
"seq": 1,
186+
"type": "request",
187+
"arguments": {
188+
"formatOptions": {
189+
"indentSize": 4,
190+
"tabSize": 4,
191+
"newLineCharacter": "\n",
192+
"convertTabsToSpaces": true,
193+
"indentStyle": 2,
194+
"insertSpaceAfterConstructor": false,
195+
"insertSpaceAfterCommaDelimiter": true,
196+
"insertSpaceAfterSemicolonInForStatements": true,
197+
"insertSpaceBeforeAndAfterBinaryOperators": true,
198+
"insertSpaceAfterKeywordsInControlFlowStatements": true,
199+
"insertSpaceAfterFunctionKeywordForAnonymousFunctions": false,
200+
"insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis": false,
201+
"insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets": false,
202+
"insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": true,
203+
"insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces": false,
204+
"insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces": false,
205+
"insertSpaceBeforeFunctionParenthesis": false,
206+
"placeOpenBraceOnNewLineForFunctions": false,
207+
"placeOpenBraceOnNewLineForControlBlocks": false,
208+
"semicolons": "ignore",
209+
"trimTrailingWhitespace": true,
210+
"indentSwitchCase": true
211+
}
212+
},
213+
"command": "configure"
214+
}
215+
Info seq [hh:mm:ss:mss] Format host information updated
216+
Info seq [hh:mm:ss:mss] response:
217+
{
218+
"seq": 0,
219+
"type": "response",
220+
"command": "configure",
221+
"request_seq": 1,
222+
"success": true
223+
}
224+
Info seq [hh:mm:ss:mss] request:
225+
{
226+
"seq": 2,
227+
"type": "request",
228+
"arguments": {
229+
"file": "/home/src/workspaces/project/folder/target.ts",
230+
"pastedText": [
231+
""
232+
],
233+
"pasteLocations": [
234+
{
235+
"start": {
236+
"line": 1,
237+
"offset": 1
238+
},
239+
"end": {
240+
"line": 1,
241+
"offset": 1
242+
}
243+
}
244+
],
245+
"copiedFrom": {
246+
"file": "/home/src/workspaces/project/a.ts",
247+
"spans": [
248+
{
249+
"start": {
250+
"line": 1,
251+
"offset": 1
252+
},
253+
"end": {
254+
"line": 1,
255+
"offset": 1
256+
}
257+
}
258+
]
259+
}
260+
},
261+
"command": "getPasteEdits"
262+
}
263+
Info seq [hh:mm:ss:mss] Starting updateGraphWorker: Project: /home/src/workspaces/project/tsconfig.json
264+
Info seq [hh:mm:ss:mss] Finishing updateGraphWorker: Project: /home/src/workspaces/project/tsconfig.json projectStateVersion: 2 projectProgramVersion: 1 structureChanged: false structureIsReused:: Completely Elapsed:: *ms
265+
Info seq [hh:mm:ss:mss] Project '/home/src/workspaces/project/tsconfig.json' (Configured)
266+
Info seq [hh:mm:ss:mss] Files (5)
267+
/home/src/tslibs/TS/Lib/lib.d.ts Text-1 lib.d.ts-Text
268+
/home/src/tslibs/TS/Lib/lib.decorators.d.ts Text-1 lib.decorators.d.ts-Text
269+
/home/src/tslibs/TS/Lib/lib.decorators.legacy.d.ts Text-1 lib.decorators.legacy.d.ts-Text
270+
/home/src/workspaces/project/a.ts Text-1 "function foo() { }\nconst x = foo()\n\nfoo()\nx"
271+
/home/src/workspaces/project/folder/target.ts SVC-1-1 ""
272+
273+
Info seq [hh:mm:ss:mss] -----------------------------------------------
274+
Info seq [hh:mm:ss:mss] response:
275+
{
276+
"seq": 0,
277+
"type": "response",
278+
"command": "getPasteEdits",
279+
"request_seq": 2,
280+
"success": true,
281+
"performanceData": {
282+
"updateGraphDurationMs": *
283+
},
284+
"body": {
285+
"edits": [],
286+
"fixId": "providePostPasteEdits"
287+
}
288+
}
289+
After Request
290+
Projects::
291+
/home/src/workspaces/project/tsconfig.json (Configured) *changed*
292+
projectStateVersion: 3 *changed*
293+
projectProgramVersion: 1
294+
dirty: true *changed*
295+
autoImportProviderHost: false
296+
297+
ScriptInfos::
298+
/home/src/tslibs/TS/Lib/lib.d.ts
299+
version: Text-1
300+
containingProjects: 1
301+
/home/src/workspaces/project/tsconfig.json
302+
/home/src/tslibs/TS/Lib/lib.decorators.d.ts
303+
version: Text-1
304+
containingProjects: 1
305+
/home/src/workspaces/project/tsconfig.json
306+
/home/src/tslibs/TS/Lib/lib.decorators.legacy.d.ts
307+
version: Text-1
308+
containingProjects: 1
309+
/home/src/workspaces/project/tsconfig.json
310+
/home/src/workspaces/project/a.ts
311+
version: Text-1
312+
containingProjects: 1
313+
/home/src/workspaces/project/tsconfig.json
314+
/home/src/workspaces/project/folder/target.ts (Open) *changed*
315+
version: SVC-1-2 *changed*
316+
containingProjects: 1
317+
/home/src/workspaces/project/tsconfig.json *default*

0 commit comments

Comments
 (0)