diff --git a/src/harness/client.ts b/src/harness/client.ts index a2fa6c4e1d37c..2d764ad15d9d6 100644 --- a/src/harness/client.ts +++ b/src/harness/client.ts @@ -789,6 +789,8 @@ export class SessionClient implements LanguageService { }); } + mapCode = notImplemented; + private createFileLocationOrRangeRequestArgs(positionOrRange: number | TextRange, fileName: string): protocol.FileLocationOrRangeRequestArgs { return typeof positionOrRange === "number" ? this.createFileLocationRequestArgs(fileName, positionOrRange) diff --git a/src/harness/fourslashImpl.ts b/src/harness/fourslashImpl.ts index 6c7a14535d2c5..8dd9884255f75 100644 --- a/src/harness/fourslashImpl.ts +++ b/src/harness/fourslashImpl.ts @@ -4510,6 +4510,32 @@ export class TestState { this.verifyCurrentFileContent(newFileContent); } + + public baselineMapCode( + { fileName, pos }: Range, + changesFilename = "/incomingChanges", + ): void { + const changes = this.getFileContent(changesFilename); + const beforeContents = this.getFileContent(fileName); + const before = beforeContents.slice(0, pos) + "[||]" + beforeContents.slice(pos); + const edits = this.languageService.mapCode( + fileName, + [changes], + [[{ start: pos, length: 1 }]], + this.formatCodeSettings, + {}, + ); + this.applyChanges(edits); + const after = this.getFileContent(fileName); + const baseline = ` +// === ORIGINAL === +${before} +// === INCOMING CHANGES === +${changes} +// === MAPPED === +${after}`; + this.baseline("mapCode", baseline, ".mapCode.ts"); + } } function updateTextRangeForTextChanges({ pos, end }: ts.TextRange, textChanges: readonly ts.TextChange[]): ts.TextRange { diff --git a/src/harness/fourslashInterfaceImpl.ts b/src/harness/fourslashInterfaceImpl.ts index 28cfa8c201d7d..a2ce5d8cd0330 100644 --- a/src/harness/fourslashInterfaceImpl.ts +++ b/src/harness/fourslashInterfaceImpl.ts @@ -239,6 +239,10 @@ export class VerifyNegatable { public uncommentSelection(newFileContent: string) { this.state.uncommentSelection(newFileContent); } + + public baselineMapCode(range: FourSlash.Range, changesFilename?: string): void { + this.state.baselineMapCode(range, changesFilename); + } } export class Verify extends VerifyNegatable { diff --git a/src/server/protocol.ts b/src/server/protocol.ts index e8d5b285811f5..05a3bf9ab6d9d 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -200,6 +200,7 @@ export const enum CommandTypes { ProvideCallHierarchyOutgoingCalls = "provideCallHierarchyOutgoingCalls", ProvideInlayHints = "provideInlayHints", WatchChange = "watchChange", + MapCode = "mapCode", } /** @@ -2342,6 +2343,38 @@ export interface InlayHintsResponse extends Response { body?: InlayHintItem[]; } +export interface MapCodeRequestArgs extends FileRequestArgs { + /** + * The files and changes to try and apply/map. + */ + mapping: MapCodeRequestDocumentMapping; +} + +export interface MapCodeRequestDocumentMapping { + /** + * The specific code to map/insert/replace in the file. + */ + contents: string[]; + + /** + * Areas of "focus" to inform the code mapper with. For example, cursor + * location, current selection, viewport, etc. Nested arrays denote + * priority: toplevel arrays are more important than inner arrays, and + * inner array priorities are based on items within that array. Items + * earlier in the arrays have higher priority. + */ + focusLocations?: TextSpan[][]; +} + +export interface MapCodeRequest extends FileRequest { + command: CommandTypes.MapCode; + arguments: MapCodeRequestArgs; +} + +export interface MapCodeResponse extends Response { + body: readonly FileCodeEdits[]; +} + /** * Synchronous request for semantic diagnostics of one file. */ diff --git a/src/server/session.ts b/src/server/session.ts index ff0a02be5b915..1d3b34d66f719 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1906,6 +1906,26 @@ export class Session implements EventSender { }); } + private mapCode(args: protocol.MapCodeRequestArgs): protocol.FileCodeEdits[] { + const formatOptions = this.getHostFormatOptions(); + const preferences = this.getHostPreferences(); + const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); + const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; + const focusLocations = args.mapping.focusLocations?.map(spans => { + return spans.map(loc => { + const start = scriptInfo.lineOffsetToPosition(loc.start.line, loc.start.offset); + const end = scriptInfo.lineOffsetToPosition(loc.end.line, loc.end.offset); + return { + start, + length: end - start, + }; + }); + }); + + const changes = languageService.mapCode(file, args.mapping.contents, focusLocations, formatOptions, preferences); + return this.mapTextChangesToCodeEdits(changes); + } + private setCompilerOptionsForInferredProjects(args: protocol.SetCompilerOptionsForInferredProjectsArgs): void { this.projectService.setCompilerOptionsForInferredProjects(args.options, args.projectRootPath); } @@ -3610,6 +3630,9 @@ export class Session implements EventSender { [protocol.CommandTypes.ProvideInlayHints]: (request: protocol.InlayHintsRequest) => { return this.requiredResponse(this.provideInlayHints(request.arguments)); }, + [protocol.CommandTypes.MapCode]: (request: protocol.MapCodeRequest) => { + return this.requiredResponse(this.mapCode(request.arguments)); + }, })); public addProtocolHandler(command: string, handler: (request: protocol.Request) => HandlerResponse) { diff --git a/src/services/_namespaces/ts.MapCode.ts b/src/services/_namespaces/ts.MapCode.ts new file mode 100644 index 0000000000000..1ef9ddd0f1283 --- /dev/null +++ b/src/services/_namespaces/ts.MapCode.ts @@ -0,0 +1,3 @@ +/* Generated file to emulate the ts.MapCode namespace. */ + +export * from "../mapCode.js"; diff --git a/src/services/_namespaces/ts.ts b/src/services/_namespaces/ts.ts index 0c09ffca05e94..b98a2f743cf59 100644 --- a/src/services/_namespaces/ts.ts +++ b/src/services/_namespaces/ts.ts @@ -33,6 +33,8 @@ export { GoToDefinition }; import * as InlayHints from "./ts.InlayHints.js"; export { InlayHints }; import * as JsDoc from "./ts.JsDoc.js"; +import * as MapCode from "./ts.MapCode.js"; +export { MapCode }; export { JsDoc }; import * as NavigateTo from "./ts.NavigateTo.js"; export { NavigateTo }; diff --git a/src/services/mapCode.ts b/src/services/mapCode.ts new file mode 100644 index 0000000000000..3765ca9c28c67 --- /dev/null +++ b/src/services/mapCode.ts @@ -0,0 +1,330 @@ +import { + Block, + ClassElement, + ClassLikeDeclaration, + createSourceFile, + emptyArray, + FileTextChanges, + find, + findAncestor, + findLast, + flatten, + forEach, + formatting, + getTokenAtPosition, + isBlock, + isClassElement, + isClassLike, + isForInOrOfStatement, + isForStatement, + isIfStatement, + isInterfaceDeclaration, + isLabeledStatement, + isNamedDeclaration, + isSourceFile, + isTypeElement, + isWhileStatement, + LanguageServiceHost, + Mutable, + Node, + NodeArray, + or, + ScriptTarget, + some, + SourceFile, + Statement, + SyntaxKind, + textChanges, + TextSpan, + TypeElement, + UserPreferences, +} from "./_namespaces/ts"; +import { ChangeTracker } from "./textChanges.js"; + +/** @internal */ +export function mapCode( + sourceFile: SourceFile, + contents: string[], + focusLocations: TextSpan[][] | undefined, + host: LanguageServiceHost, + formatContext: formatting.FormatContext, + preferences: UserPreferences, +): FileTextChanges[] { + try { + return textChanges.ChangeTracker.with( + { host, formatContext, preferences }, + changeTracker => { + const parsed = contents.map(parse); + const flattenedLocations = focusLocations && flatten(focusLocations); + for (const nodes of parsed) { + placeNodeGroup( + sourceFile, + changeTracker, + nodes, + flattenedLocations, + ); + } + }, + ); + } + catch (e) { + host.error?.(`mapCode: ${e}`); + return emptyArray; + } +} + +/** + * Tries to parse something into either "top-level" statements, or into blocks + * of class-context definitions. + */ +function parse(content: string): NodeArray { + // We're going to speculatively parse different kinds of contexts to see + // which one makes the most sense, and grab the NodeArray from there. Do + // this as lazily as possible. + const nodeKinds = [ + { + parse: () => + createSourceFile( + "__mapcode_content_nodes.ts", + content, + ScriptTarget.Latest, + /*setParentNodes*/ true, + ), + body: (sf: SourceFile) => sf.statements, + }, + { + parse: () => + createSourceFile( + "__mapcode_class_content_nodes.ts", + `class __class {\n${content}\n}`, + ScriptTarget.Latest, + /*setParentNodes*/ true, + ), + body: (cw: SourceFile) => (cw.statements[0] as ClassLikeDeclaration).members, + }, + ]; + + const parsedNodes = []; + for (const { parse, body } of nodeKinds) { + const sourceFile = parse(); + const bod = body(sourceFile); + if (bod.length && sourceFile.parseDiagnostics.length === 0) { + // If we run into a case with no parse errors, this is likely the right kind. + return bod; + } + // We only want to keep the ones that have some kind of body. + else if (bod.length) { + // Otherwise, we'll need to look at others. + parsedNodes.push({ sourceFile, body: bod }); + } + } + // Heuristic: fewer errors = more likely to be the right kind. + const { body } = parsedNodes.sort( + (a, b) => + a.sourceFile.parseDiagnostics.length - + b.sourceFile.parseDiagnostics.length, + )[0]; + return body; +} + +function placeNodeGroup( + originalFile: SourceFile, + changeTracker: ChangeTracker, + changes: NodeArray, + focusLocations?: TextSpan[], +) { + if (isClassElement(changes[0]) || isTypeElement(changes[0])) { + placeClassNodeGroup( + originalFile, + changeTracker, + changes as NodeArray, + focusLocations, + ); + } + else { + placeStatements( + originalFile, + changeTracker, + changes as NodeArray, + focusLocations, + ); + } +} + +function placeClassNodeGroup( + originalFile: SourceFile, + changeTracker: ChangeTracker, + changes: NodeArray | NodeArray, + focusLocations?: TextSpan[], +) { + let classOrInterface; + if (!focusLocations || !focusLocations.length) { + classOrInterface = find(originalFile.statements, or(isClassLike, isInterfaceDeclaration)); + } + else { + classOrInterface = forEach(focusLocations, location => + findAncestor( + getTokenAtPosition(originalFile, location.start), + or(isClassLike, isInterfaceDeclaration), + )); + } + + if (!classOrInterface) { + throw new Error( + "Failed to find a class or interface to map the given code into.", + ); + } + + const firstMatch = classOrInterface.members.find(member => changes.some(change => matchNode(change, member))); + if (firstMatch) { + // can't be undefined here, since we know we have at least one match. + const lastMatch = findLast( + classOrInterface.members as NodeArray, + member => changes.some(change => matchNode(change, member)), + )!; + + forEach(changes, wipeNode); + changeTracker.replaceNodeRangeWithNodes( + originalFile, + firstMatch, + lastMatch, + changes, + ); + return; + } + + forEach(changes, wipeNode); + changeTracker.insertNodesAfter( + originalFile, + classOrInterface.members[classOrInterface.members.length - 1], + changes, + ); +} + +function placeStatements( + originalFile: SourceFile, + changeTracker: ChangeTracker, + changes: NodeArray, + focusLocations?: TextSpan[], +) { + if (!focusLocations?.length) { + changeTracker.insertNodesAtEndOfFile( + originalFile, + changes, + /*blankLineBetween*/ false, + ); + return; + } + + for (const location of focusLocations) { + const scope = findAncestor( + getTokenAtPosition(originalFile, location.start), + (block): block is Block | SourceFile => + or(isBlock, isSourceFile)(block) && + some(block.statements, origStmt => changes.some(newStmt => matchNode(newStmt, origStmt))), + ); + if (scope) { + const start = scope.statements.find(stmt => changes.some(node => matchNode(node, stmt))); + if (start) { + // Can't be undefined here, since we know we have at least one match. + const end = findLast(scope.statements, stmt => changes.some(node => matchNode(node, stmt)))!; + forEach(changes, wipeNode); + changeTracker.replaceNodeRangeWithNodes( + originalFile, + start, + end, + changes, + ); + return; + } + } + } + + let scopeStatements: NodeArray = originalFile.statements; + for (const location of focusLocations) { + const block = findAncestor( + getTokenAtPosition(originalFile, location.start), + isBlock, + ); + if (block) { + scopeStatements = block.statements; + break; + } + } + forEach(changes, wipeNode); + changeTracker.insertNodesAfter( + originalFile, + scopeStatements[scopeStatements.length - 1], + changes, + ); +} + +function matchNode(a: Node, b: Node): boolean { + if (a.kind !== b.kind) { + return false; + } + + if (a.kind === SyntaxKind.Constructor) { + return a.kind === b.kind; + } + + if (isNamedDeclaration(a) && isNamedDeclaration(b)) { + return a.name.getText() === b.name.getText(); + } + + if (isIfStatement(a) && isIfStatement(b)) { + return ( + a.expression.getText() === b.expression.getText() + ); + } + + if (isWhileStatement(a) && isWhileStatement(b)) { + return ( + a.expression.getText() === + b.expression.getText() + ); + } + + if (isForStatement(a) && isForStatement(b)) { + return ( + a.initializer?.getText() === + b.initializer?.getText() && + a.incrementor?.getText() === + b.incrementor?.getText() && + a.condition?.getText() === b.condition?.getText() + ); + } + + if (isForInOrOfStatement(a) && isForInOrOfStatement(b)) { + return ( + a.expression.getText() === + b.expression.getText() && + a.initializer.getText() === + b.initializer.getText() + ); + } + + if (isLabeledStatement(a) && isLabeledStatement(b)) { + // If we're actually labeling/naming something, we should be a bit + // more lenient about when we match, so we don't care what the actual + // related statement is: we just replace. + return a.label.getText() === b.label.getText(); + } + + if (a.getText() === b.getText()) { + return true; + } + + return false; +} + +function wipeNode(node: Mutable) { + resetNodePositions(node); + node.parent = undefined!; +} + +function resetNodePositions(node: Mutable) { + node.pos = -1; + node.end = -1; + node.forEachChild(resetNodePositions); +} diff --git a/src/services/services.ts b/src/services/services.ts index b46a5956dd4e8..bc597547180e0 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -211,6 +211,7 @@ import { LinkedEditingInfo, LiteralType, map, + MapCode, mapDefined, MapLike, mapOneOrMany, @@ -3165,6 +3166,17 @@ export function createLanguageService( return InlayHints.provideInlayHints(getInlayHintsContext(sourceFile, span, preferences)); } + function mapCode(sourceFile: string, contents: string[], focusLocations: TextSpan[][] | undefined, formatOptions: FormatCodeSettings, preferences: UserPreferences = emptyOptions): FileTextChanges[] { + return MapCode.mapCode( + syntaxTreeCache.getCurrentSourceFile(sourceFile), + contents, + focusLocations, + host, + formatting.getFormatContext(formatOptions, host), + preferences, + ); + } + const ls: LanguageService = { dispose, cleanupSemanticCache, @@ -3236,6 +3248,7 @@ export function createLanguageService( provideInlayHints, getSupportedCodeFixes, getPasteEdits, + mapCode, }; switch (languageServiceMode) { diff --git a/src/services/types.ts b/src/services/types.ts index 7d1b10311f85e..eeff286d29ea8 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -683,6 +683,8 @@ export interface LanguageService { getSupportedCodeFixes(fileName?: string): readonly string[]; + /** @internal */ mapCode(fileName: string, contents: string[], focusLocations: TextSpan[][] | undefined, formatOptions: FormatCodeSettings, preferences: UserPreferences, updates?: FileTextChanges[]): readonly FileTextChanges[]; + dispose(): void; getPasteEdits( args: PasteEditsArgs, @@ -1588,6 +1590,13 @@ export interface OutliningSpan { kind: OutliningSpanKind; } +/** @internal */ +export interface MapCodeDocumentMapping { + fileName: string; + focusLocations?: TextSpan[][]; + contents: string[]; +} + export const enum OutliningSpanKind { /** Single or multi-line comments */ Comment = "comment", diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 59692349afdc9..13a2701e05b4e 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -121,6 +121,7 @@ declare namespace ts { ProvideCallHierarchyOutgoingCalls = "provideCallHierarchyOutgoingCalls", ProvideInlayHints = "provideInlayHints", WatchChange = "watchChange", + MapCode = "mapCode", } /** * A TypeScript Server message @@ -1769,6 +1770,46 @@ declare namespace ts { export interface InlayHintsResponse extends Response { body?: InlayHintItem[]; } + export interface MapCodeRequestArgs { + /** + * The files and changes to try and apply/map. + */ + mappings: MapCodeRequestDocumentMapping[]; + /** + * Edits to apply before performing the mapping. + */ + updates?: FileCodeEdits[]; + } + export interface MapCodeRequestDocumentMapping { + /** + * The file for the request (absolute pathname required). `undefined` + * if specific file is unknown. + */ + file?: string; + /** + * Optional name of project that contains file + */ + projectFileName?: string; + /** + * The specific code to map/insert/replace in the file. + */ + contents: string[]; + /** + * Areas of "focus" to inform the code mapper with. For example, cursor + * location, current selection, viewport, etc. Nested arrays denote + * priority: toplevel arrays are more important than inner arrays, and + * inner array priorities are based on items within that array. Items + * earlier in the arrays have higher priority. + */ + focusLocations?: FileSpan[][]; + } + export interface MapCodeRequest extends Request { + command: CommandTypes.MapCode; + arguments: MapCodeRequestArgs; + } + export interface MapCodeResponse extends Response { + body: FileCodeEdits[]; + } /** * Synchronous request for semantic diagnostics of one file. */ @@ -3469,6 +3510,7 @@ declare namespace ts { private getLinkedEditingRange; private getDocumentHighlights; private provideInlayHints; + private mapCode; private setCompilerOptionsForInferredProjects; private getProjectInfo; private getProjectInfoWorker; diff --git a/tests/baselines/reference/mapCodeClassInvalidClassMember.mapCode.ts b/tests/baselines/reference/mapCodeClassInvalidClassMember.mapCode.ts new file mode 100644 index 0000000000000..10aaee3330526 --- /dev/null +++ b/tests/baselines/reference/mapCodeClassInvalidClassMember.mapCode.ts @@ -0,0 +1,18 @@ +// === mapCode === + +// === ORIGINAL === +class MyClass {[||] +} + +// === INCOMING CHANGES === +if (false) { + return "hello"; +} + +// === MAPPED === +class MyClass { +} + +if (false) { + return "hello"; +} diff --git a/tests/baselines/reference/mapCodeFocusLocationReplacement.mapCode.ts b/tests/baselines/reference/mapCodeFocusLocationReplacement.mapCode.ts new file mode 100644 index 0000000000000..193db003fc193 --- /dev/null +++ b/tests/baselines/reference/mapCodeFocusLocationReplacement.mapCode.ts @@ -0,0 +1,63 @@ +// === mapCode === + +// === ORIGINAL === +function foo() { + const x: number = 1; + const y: number = 2; + if (x === y) { + console.log("hello"); + console.log("world"); + } + return 1; +} +function bar() { + const x: number = 1; + const y: number = 2; + if (x === y)[||] { + console.log("x"); + console.log("y"); + } + return 2; +} +function baz() { + const x: number = 1; + const y: number = 2; + if (x === y) { + console.log(x); + console.log(y); + } + return 3; +} + +// === INCOMING CHANGES === +if (x === y) { + return x + y; +} + +// === MAPPED === +function foo() { + const x: number = 1; + const y: number = 2; + if (x === y) { + console.log("hello"); + console.log("world"); + } + return 1; +} +function bar() { + const x: number = 1; + const y: number = 2; + if (x === y) { + return x + y; + } + return 2; +} +function baz() { + const x: number = 1; + const y: number = 2; + if (x === y) { + console.log(x); + console.log(y); + } + return 3; +} diff --git a/tests/baselines/reference/mapCodeMethodInsertion.mapCode.ts b/tests/baselines/reference/mapCodeMethodInsertion.mapCode.ts new file mode 100644 index 0000000000000..a370254451a3e --- /dev/null +++ b/tests/baselines/reference/mapCodeMethodInsertion.mapCode.ts @@ -0,0 +1,37 @@ +// === mapCode === + +// === ORIGINAL === +class MyClass {[||] + x = 1; + foo() { + return 1; + } + bar() { + return 2; + } + baz() { + return 3; + } +} + +// === INCOMING CHANGES === +quux() { + return 4; +} + +// === MAPPED === +class MyClass { + x = 1; + foo() { + return 1; + } + bar() { + return 2; + } + baz() { + return 3; + } + quux() { + return 4; + } +} diff --git a/tests/baselines/reference/mapCodeMethodReplacement.mapCode.ts b/tests/baselines/reference/mapCodeMethodReplacement.mapCode.ts new file mode 100644 index 0000000000000..67277b119faa0 --- /dev/null +++ b/tests/baselines/reference/mapCodeMethodReplacement.mapCode.ts @@ -0,0 +1,34 @@ +// === mapCode === + +// === ORIGINAL === +class MyClass { + x = 1; + foo() { + return 1; + } + bar() {[||] + return 2; + } + baz() { + return 3; + } +} + +// === INCOMING CHANGES === +bar() { + return 'hello'; +} + +// === MAPPED === +class MyClass { + x = 1; + foo() { + return 1; + } + bar() { + return "hello"; + } + baz() { + return 3; + } +} diff --git a/tests/baselines/reference/mapCodeMixedClassDeclaration.mapCode.ts b/tests/baselines/reference/mapCodeMixedClassDeclaration.mapCode.ts new file mode 100644 index 0000000000000..796b9b37e8248 --- /dev/null +++ b/tests/baselines/reference/mapCodeMixedClassDeclaration.mapCode.ts @@ -0,0 +1,34 @@ +// === mapCode === + +// === ORIGINAL === +class MyClass {[||] + x = 1; + foo() { + return 1; + } + bar() { + return 2; + } +} + +// === INCOMING CHANGES === +x = 3; +bar() { + return 'hello'; +} +baz() { + return 3; +} +y = 2; + +// === MAPPED === +class MyClass { + x = 3; + bar() { + return "hello"; + } + baz() { + return 3; + } + y = 2; +} diff --git a/tests/baselines/reference/mapCodeNestedClassIfInsertion.mapCode.ts b/tests/baselines/reference/mapCodeNestedClassIfInsertion.mapCode.ts new file mode 100644 index 0000000000000..0dcf31d7a0bc8 --- /dev/null +++ b/tests/baselines/reference/mapCodeNestedClassIfInsertion.mapCode.ts @@ -0,0 +1,41 @@ +// === mapCode === + +// === ORIGINAL === +class MyClass { + x = 1; + foo() { + return 1; + } + bar() { + if (true)[||] { + return 2; + } + } + baz() { + return 3; + } +} + +// === INCOMING CHANGES === +if (false) { + return "hello"; +} + +// === MAPPED === +class MyClass { + x = 1; + foo() { + return 1; + } + bar() { + if (true) { + return 2; + if (false) { + return "hello"; + } + } + } + baz() { + return 3; + } +} diff --git a/tests/baselines/reference/mapCodeNestedClassIfReplacement.mapCode.ts b/tests/baselines/reference/mapCodeNestedClassIfReplacement.mapCode.ts new file mode 100644 index 0000000000000..44bf9f2bf944d --- /dev/null +++ b/tests/baselines/reference/mapCodeNestedClassIfReplacement.mapCode.ts @@ -0,0 +1,38 @@ +// === mapCode === + +// === ORIGINAL === +class MyClass { + x = 1; + foo() { + return 1; + } + bar() { + if (true) [||]{ + return 2; + } + } + baz() { + return 3; + } +} + +// === INCOMING CHANGES === +if (true) { + return "hello"; +} + +// === MAPPED === +class MyClass { + x = 1; + foo() { + return 1; + } + bar() { + if (true) { + return "hello"; + } + } + baz() { + return 3; + } +} diff --git a/tests/baselines/reference/mapCodeNestedForInsertion.mapCode.ts b/tests/baselines/reference/mapCodeNestedForInsertion.mapCode.ts new file mode 100644 index 0000000000000..fa175a9a5b314 --- /dev/null +++ b/tests/baselines/reference/mapCodeNestedForInsertion.mapCode.ts @@ -0,0 +1,33 @@ +// === mapCode === + +// === ORIGINAL === +function foo() { + const x: number = 1; + const y: number = 2; + for (let i = 0; i < 10; i++) [||]{ + console.log("hello"); + console.log("you"); + } + return 1; +} + +// === INCOMING CHANGES === +for (let j = 0; j < 10; j++) { + console.log("goodbye"); + console.log("world"); +} + +// === MAPPED === +function foo() { + const x: number = 1; + const y: number = 2; + for (let i = 0; i < 10; i++) { + console.log("hello"); + console.log("you"); + for (let j = 0; j < 10; j++) { + console.log("goodbye"); + console.log("world"); + } + } + return 1; +} diff --git a/tests/baselines/reference/mapCodeNestedForOfInsertion.mapCode.ts b/tests/baselines/reference/mapCodeNestedForOfInsertion.mapCode.ts new file mode 100644 index 0000000000000..e71d23085d6c0 --- /dev/null +++ b/tests/baselines/reference/mapCodeNestedForOfInsertion.mapCode.ts @@ -0,0 +1,29 @@ +// === mapCode === + +// === ORIGINAL === +function foo() { + for (const x of [1, 2, 3])[||] { + console.log("hello"); + console.log("you"); + } + return 1; +} + +// === INCOMING CHANGES === +for (const y of [1, 2, 3]) { + console.log("goodbye"); + console.log("world"); +} + +// === MAPPED === +function foo() { + for (const x of [1, 2, 3]) { + console.log("hello"); + console.log("you"); + for (const y of [1, 2, 3]) { + console.log("goodbye"); + console.log("world"); + } + } + return 1; +} diff --git a/tests/baselines/reference/mapCodeNestedForOfReplacement.mapCode.ts b/tests/baselines/reference/mapCodeNestedForOfReplacement.mapCode.ts new file mode 100644 index 0000000000000..1ba34b4ffdabe --- /dev/null +++ b/tests/baselines/reference/mapCodeNestedForOfReplacement.mapCode.ts @@ -0,0 +1,25 @@ +// === mapCode === + +// === ORIGINAL === +function foo() { + for (const x of [1, 2, 3]) [||]{ + console.log("hello"); + console.log("you"); + } + return 1; +} + +// === INCOMING CHANGES === +for (const x of [1, 2, 3]) { + console.log("goodbye"); + console.log("world"); +} + +// === MAPPED === +function foo() { + for (const x of [1, 2, 3]) { + console.log("goodbye"); + console.log("world"); + } + return 1; +} diff --git a/tests/baselines/reference/mapCodeNestedForReplacement.mapCode.ts b/tests/baselines/reference/mapCodeNestedForReplacement.mapCode.ts new file mode 100644 index 0000000000000..1194d4f6d207c --- /dev/null +++ b/tests/baselines/reference/mapCodeNestedForReplacement.mapCode.ts @@ -0,0 +1,25 @@ +// === mapCode === + +// === ORIGINAL === +function foo() { + for (let x = 0; x < 10; x++) [||]{ + console.log("hello"); + console.log("you"); + } + return 1; +} + +// === INCOMING CHANGES === +for (let x = 0; x < 10; x++) { + console.log("goodbye"); + console.log("world"); +} + +// === MAPPED === +function foo() { + for (let x = 0; x < 10; x++) { + console.log("goodbye"); + console.log("world"); + } + return 1; +} diff --git a/tests/baselines/reference/mapCodeNestedIfInsertion.mapCode.ts b/tests/baselines/reference/mapCodeNestedIfInsertion.mapCode.ts new file mode 100644 index 0000000000000..2b82da88c7faa --- /dev/null +++ b/tests/baselines/reference/mapCodeNestedIfInsertion.mapCode.ts @@ -0,0 +1,33 @@ +// === mapCode === + +// === ORIGINAL === +function foo() { + const x: number = 1; + const y: number = 2; + if (x === y) [||]{ + console.log("hello"); + console.log("you"); + } + return 1; +} + +// === INCOMING CHANGES === +if (y === x) { + console.log("goodbye"); + console.log("world"); +} + +// === MAPPED === +function foo() { + const x: number = 1; + const y: number = 2; + if (x === y) { + console.log("hello"); + console.log("you"); + if (y === x) { + console.log("goodbye"); + console.log("world"); + } + } + return 1; +} diff --git a/tests/baselines/reference/mapCodeNestedIfReplace.mapCode.ts b/tests/baselines/reference/mapCodeNestedIfReplace.mapCode.ts new file mode 100644 index 0000000000000..e3042ffe9c751 --- /dev/null +++ b/tests/baselines/reference/mapCodeNestedIfReplace.mapCode.ts @@ -0,0 +1,29 @@ +// === mapCode === + +// === ORIGINAL === +function foo() { + const x: number = 1; + const y: number = 2; + if (x === y) [||]{ + console.log("hello"); + console.log("you"); + } + return 1; +} + +// === INCOMING CHANGES === +if (x === y) { + console.log("goodbye"); + console.log("world"); +} + +// === MAPPED === +function foo() { + const x: number = 1; + const y: number = 2; + if (x === y) { + console.log("goodbye"); + console.log("world"); + } + return 1; +} diff --git a/tests/baselines/reference/mapCodeNestedLabeledInsertion.mapCode.ts b/tests/baselines/reference/mapCodeNestedLabeledInsertion.mapCode.ts new file mode 100644 index 0000000000000..24f89c88646d4 --- /dev/null +++ b/tests/baselines/reference/mapCodeNestedLabeledInsertion.mapCode.ts @@ -0,0 +1,37 @@ +// === mapCode === + +// === ORIGINAL === +function foo() { + const x: number = 1; + const y: number = 2; + myLabel: if (x === y) [||]{ + console.log("hello"); + console.log("you"); + break myLabel; + } + return 1; +} + +// === INCOMING CHANGES === +otherLabel: if (y === x) { + console.log("goodbye"); + console.log("world"); + break otherLabel; +} + +// === MAPPED === +function foo() { + const x: number = 1; + const y: number = 2; + myLabel: if (x === y) { + console.log("hello"); + console.log("you"); + break myLabel; + otherLabel: if (y === x) { + console.log("goodbye"); + console.log("world"); + break otherLabel; + } + } + return 1; +} diff --git a/tests/baselines/reference/mapCodeNestedLabeledReplace.mapCode.ts b/tests/baselines/reference/mapCodeNestedLabeledReplace.mapCode.ts new file mode 100644 index 0000000000000..114ce09b8d4c9 --- /dev/null +++ b/tests/baselines/reference/mapCodeNestedLabeledReplace.mapCode.ts @@ -0,0 +1,32 @@ +// === mapCode === + +// === ORIGINAL === +function foo() { + const x: number = 1; + const y: number = 2; + myLabel: if (x === y) [||]{ + console.log("hello"); + console.log("you"); + break myLabel; + } + return 1; +} + +// === INCOMING CHANGES === +myLabel: if (y === x) { + console.log("goodbye"); + console.log("world"); + break myLabel; +} + +// === MAPPED === +function foo() { + const x: number = 1; + const y: number = 2; + myLabel: if (y === x) { + console.log("goodbye"); + console.log("world"); + break myLabel; + } + return 1; +} diff --git a/tests/baselines/reference/mapCodeNestedWhileInsertion.mapCode.ts b/tests/baselines/reference/mapCodeNestedWhileInsertion.mapCode.ts new file mode 100644 index 0000000000000..2e6862eadf816 --- /dev/null +++ b/tests/baselines/reference/mapCodeNestedWhileInsertion.mapCode.ts @@ -0,0 +1,33 @@ +// === mapCode === + +// === ORIGINAL === +function foo() { + const x: number = 1; + const y: number = 2; + while (x === y) [||]{ + console.log("hello"); + console.log("you"); + } + return 1; +} + +// === INCOMING CHANGES === +while (y === x) { + console.log("goodbye"); + console.log("world"); +} + +// === MAPPED === +function foo() { + const x: number = 1; + const y: number = 2; + while (x === y) { + console.log("hello"); + console.log("you"); + while (y === x) { + console.log("goodbye"); + console.log("world"); + } + } + return 1; +} diff --git a/tests/baselines/reference/mapCodeNestedWhileReplace.mapCode.ts b/tests/baselines/reference/mapCodeNestedWhileReplace.mapCode.ts new file mode 100644 index 0000000000000..d93bdc1e9e23a --- /dev/null +++ b/tests/baselines/reference/mapCodeNestedWhileReplace.mapCode.ts @@ -0,0 +1,29 @@ +// === mapCode === + +// === ORIGINAL === +function foo() { + const x: number = 1; + const y: number = 2; + while (x === y) [||]{ + console.log("hello"); + console.log("you"); + } + return 1; +} + +// === INCOMING CHANGES === +while (x === y) { + console.log("goodbye"); + console.log("world"); +} + +// === MAPPED === +function foo() { + const x: number = 1; + const y: number = 2; + while (x === y) { + console.log("goodbye"); + console.log("world"); + } + return 1; +} diff --git a/tests/baselines/reference/mapCodeRangeReplacement.mapCode.ts b/tests/baselines/reference/mapCodeRangeReplacement.mapCode.ts new file mode 100644 index 0000000000000..3d4eb3715f918 --- /dev/null +++ b/tests/baselines/reference/mapCodeRangeReplacement.mapCode.ts @@ -0,0 +1,43 @@ +// === mapCode === + +// === ORIGINAL === +function foo() { + return 1; +} +if (foo == bar) { + console.log("hello"); + console.log("you"); +} +[||] +function bar() { + return 2; +} +function baz() { + return 3; +} +while (false) { + console.log("weee"); +} + +// === INCOMING CHANGES === +if (foo == bar) { + console.log("huh"); +} + +function baz() { + return 'baz'; +} + +// === MAPPED === +function foo() { + return 1; +} +if (foo == bar) { + console.log("huh"); +} +function baz() { + return "baz"; +} +while (false) { + console.log("weee"); +} diff --git a/tests/baselines/reference/mapCodeToplevelInsert.mapCode.ts b/tests/baselines/reference/mapCodeToplevelInsert.mapCode.ts new file mode 100644 index 0000000000000..d0f943c45867a --- /dev/null +++ b/tests/baselines/reference/mapCodeToplevelInsert.mapCode.ts @@ -0,0 +1,24 @@ +// === mapCode === + +// === ORIGINAL === +function foo() { + return 1; +}[||] +function bar() { + return 2; +} +// === INCOMING CHANGES === +function baz() { + return 3; +} + +// === MAPPED === +function foo() { + return 1; +} +function bar() { + return 2; +} +function baz() { + return 3; +} diff --git a/tests/baselines/reference/mapCodeToplevelReplace.mapCode.ts b/tests/baselines/reference/mapCodeToplevelReplace.mapCode.ts new file mode 100644 index 0000000000000..4e4f6b42e89b0 --- /dev/null +++ b/tests/baselines/reference/mapCodeToplevelReplace.mapCode.ts @@ -0,0 +1,22 @@ +// === mapCode === + +// === ORIGINAL === +function foo() { + return 1; +}[||] +function bar() { + return 2; +} + +// === INCOMING CHANGES === +function foo() { + return 3; +} + +// === MAPPED === +function foo() { + return 3; +} +function bar() { + return 2; +} diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index e892d9d98725c..e76d4e4ebb63a 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -212,6 +212,20 @@ declare namespace FourSlashInterface { start: number; end: number; } + export interface TextChange { + span: TextSpan; + newText: string; + } + export interface FileTextChanges { + fileName: string; + textChanges: readonly TextChange[]; + isNewFile?: boolean; + } + export interface MapCodeDocumentMapping { + fileName?: string; + focusLocations?: TextSpan[][]; + contents: string[]; + } class test_ { markers(): Marker[]; markerNames(): string[]; @@ -452,6 +466,7 @@ declare namespace FourSlashInterface { copiedFrom?: { file: string, range: { pos: number, end: number }[] }; } }): void; + baselineMapCode(range: Range, changesFilename?: string): void; } class edit { caretPosition(): Marker; diff --git a/tests/cases/fourslash/mapCodeClassInvalidClassMember.ts b/tests/cases/fourslash/mapCodeClassInvalidClassMember.ts new file mode 100644 index 0000000000000..3b054d2cf46cb --- /dev/null +++ b/tests/cases/fourslash/mapCodeClassInvalidClassMember.ts @@ -0,0 +1,13 @@ +/// + +// @Filename: /incomingChanges +//// if (false) { +//// return "hello"; +//// } +//// +// @Filename: /index.ts +//// class MyClass {[||] +//// } +//// + +verify.baselineMapCode(test.ranges()[0]); \ No newline at end of file diff --git a/tests/cases/fourslash/mapCodeFocusLocationReplacement.ts b/tests/cases/fourslash/mapCodeFocusLocationReplacement.ts new file mode 100644 index 0000000000000..14ec325e68342 --- /dev/null +++ b/tests/cases/fourslash/mapCodeFocusLocationReplacement.ts @@ -0,0 +1,38 @@ +/// + +// @Filename: /incomingChanges +//// if (x === y) { +//// return x + y; +//// } +//// +// @Filename: /index.ts +//// function foo() { +//// const x: number = 1; +//// const y: number = 2; +//// if (x === y) { +//// console.log("hello"); +//// console.log("world"); +//// } +//// return 1; +//// } +//// function bar() { +//// const x: number = 1; +//// const y: number = 2; +//// if (x === y)[||] { +//// console.log("x"); +//// console.log("y"); +//// } +//// return 2; +//// } +//// function baz() { +//// const x: number = 1; +//// const y: number = 2; +//// if (x === y) { +//// console.log(x); +//// console.log(y); +//// } +//// return 3; +//// } +//// + +verify.baselineMapCode(test.ranges()[0]); \ No newline at end of file diff --git a/tests/cases/fourslash/mapCodeMethodInsertion.ts b/tests/cases/fourslash/mapCodeMethodInsertion.ts new file mode 100644 index 0000000000000..83f54ae6712bb --- /dev/null +++ b/tests/cases/fourslash/mapCodeMethodInsertion.ts @@ -0,0 +1,23 @@ +/// + +// @Filename: /incomingChanges +//// quux() { +//// return 4; +//// } +//// +// @Filename: /index.ts +//// class MyClass {[||] +//// x = 1; +//// foo() { +//// return 1; +//// } +//// bar() { +//// return 2; +//// } +//// baz() { +//// return 3; +//// } +//// } +//// + +verify.baselineMapCode(test.ranges()[0]); \ No newline at end of file diff --git a/tests/cases/fourslash/mapCodeMethodReplacement.ts b/tests/cases/fourslash/mapCodeMethodReplacement.ts new file mode 100644 index 0000000000000..2fb49e0b9031a --- /dev/null +++ b/tests/cases/fourslash/mapCodeMethodReplacement.ts @@ -0,0 +1,23 @@ +/// + +// @Filename: /incomingChanges +//// bar() { +//// return 'hello'; +//// } +//// +// @Filename: /index.ts +//// class MyClass { +//// x = 1; +//// foo() { +//// return 1; +//// } +//// bar() {[||] +//// return 2; +//// } +//// baz() { +//// return 3; +//// } +//// } +//// + +verify.baselineMapCode(test.ranges()[0]); diff --git a/tests/cases/fourslash/mapCodeMixedClassDeclaration.ts b/tests/cases/fourslash/mapCodeMixedClassDeclaration.ts new file mode 100644 index 0000000000000..3c8de306243be --- /dev/null +++ b/tests/cases/fourslash/mapCodeMixedClassDeclaration.ts @@ -0,0 +1,25 @@ +/// + +// @Filename: /incomingChanges +//// x = 3; +//// bar() { +//// return 'hello'; +//// } +//// baz() { +//// return 3; +//// } +//// y = 2; +//// +// @Filename: /index.ts +//// class MyClass {[||] +//// x = 1; +//// foo() { +//// return 1; +//// } +//// bar() { +//// return 2; +//// } +//// } +//// + +verify.baselineMapCode(test.ranges()[0]); \ No newline at end of file diff --git a/tests/cases/fourslash/mapCodeNestedClassIfInsertion.ts b/tests/cases/fourslash/mapCodeNestedClassIfInsertion.ts new file mode 100644 index 0000000000000..485f6d9bdbcdc --- /dev/null +++ b/tests/cases/fourslash/mapCodeNestedClassIfInsertion.ts @@ -0,0 +1,25 @@ +/// + +// @Filename: /incomingChanges +//// if (false) { +//// return "hello"; +//// } +//// +// @Filename: /index.ts +//// class MyClass { +//// x = 1; +//// foo() { +//// return 1; +//// } +//// bar() { +//// if (true)[||] { +//// return 2; +//// } +//// } +//// baz() { +//// return 3; +//// } +//// } +//// + +verify.baselineMapCode(test.ranges()[0]); \ No newline at end of file diff --git a/tests/cases/fourslash/mapCodeNestedClassIfReplacement.ts b/tests/cases/fourslash/mapCodeNestedClassIfReplacement.ts new file mode 100644 index 0000000000000..092ded7f39229 --- /dev/null +++ b/tests/cases/fourslash/mapCodeNestedClassIfReplacement.ts @@ -0,0 +1,25 @@ +/// + +// @Filename: /incomingChanges +//// if (true) { +//// return "hello"; +//// } +//// +// @Filename: /index.ts +//// class MyClass { +//// x = 1; +//// foo() { +//// return 1; +//// } +//// bar() { +//// if (true) [||]{ +//// return 2; +//// } +//// } +//// baz() { +//// return 3; +//// } +//// } +//// + +verify.baselineMapCode(test.ranges()[0]); \ No newline at end of file diff --git a/tests/cases/fourslash/mapCodeNestedForInsertion.ts b/tests/cases/fourslash/mapCodeNestedForInsertion.ts new file mode 100644 index 0000000000000..1d6502e914aaf --- /dev/null +++ b/tests/cases/fourslash/mapCodeNestedForInsertion.ts @@ -0,0 +1,21 @@ +/// + +// @Filename: /incomingChanges +//// for (let j = 0; j < 10; j++) { +//// console.log("goodbye"); +//// console.log("world"); +//// } +//// +// @Filename: /index.ts +//// function foo() { +//// const x: number = 1; +//// const y: number = 2; +//// for (let i = 0; i < 10; i++) [||]{ +//// console.log("hello"); +//// console.log("you"); +//// } +//// return 1; +//// } +//// + +verify.baselineMapCode(test.ranges()[0]); \ No newline at end of file diff --git a/tests/cases/fourslash/mapCodeNestedForOfInsertion.ts b/tests/cases/fourslash/mapCodeNestedForOfInsertion.ts new file mode 100644 index 0000000000000..84064dc197d54 --- /dev/null +++ b/tests/cases/fourslash/mapCodeNestedForOfInsertion.ts @@ -0,0 +1,19 @@ +/// + +// @Filename: /incomingChanges +//// for (const y of [1, 2, 3]) { +//// console.log("goodbye"); +//// console.log("world"); +//// } +//// +// @Filename: /index.ts +//// function foo() { +//// for (const x of [1, 2, 3])[||] { +//// console.log("hello"); +//// console.log("you"); +//// } +//// return 1; +//// } +//// + +verify.baselineMapCode(test.ranges()[0]); \ No newline at end of file diff --git a/tests/cases/fourslash/mapCodeNestedForOfReplacement.ts b/tests/cases/fourslash/mapCodeNestedForOfReplacement.ts new file mode 100644 index 0000000000000..2ecf12583df83 --- /dev/null +++ b/tests/cases/fourslash/mapCodeNestedForOfReplacement.ts @@ -0,0 +1,19 @@ +/// + +// @Filename: /incomingChanges +//// for (const x of [1, 2, 3]) { +//// console.log("goodbye"); +//// console.log("world"); +//// } +//// +// @Filename: /index.ts +//// function foo() { +//// for (const x of [1, 2, 3]) [||]{ +//// console.log("hello"); +//// console.log("you"); +//// } +//// return 1; +//// } +//// + +verify.baselineMapCode(test.ranges()[0]); \ No newline at end of file diff --git a/tests/cases/fourslash/mapCodeNestedForReplacement.ts b/tests/cases/fourslash/mapCodeNestedForReplacement.ts new file mode 100644 index 0000000000000..131ae92d8c1d3 --- /dev/null +++ b/tests/cases/fourslash/mapCodeNestedForReplacement.ts @@ -0,0 +1,19 @@ +/// + +// @Filename: /incomingChanges +//// for (let x = 0; x < 10; x++) { +//// console.log("goodbye"); +//// console.log("world"); +//// } +//// +// @Filename: /index.ts +//// function foo() { +//// for (let x = 0; x < 10; x++) [||]{ +//// console.log("hello"); +//// console.log("you"); +//// } +//// return 1; +//// } +//// + +verify.baselineMapCode(test.ranges()[0]); \ No newline at end of file diff --git a/tests/cases/fourslash/mapCodeNestedIfInsertion.ts b/tests/cases/fourslash/mapCodeNestedIfInsertion.ts new file mode 100644 index 0000000000000..1bdacfdb1ebf7 --- /dev/null +++ b/tests/cases/fourslash/mapCodeNestedIfInsertion.ts @@ -0,0 +1,21 @@ +/// + +// @Filename: /incomingChanges +//// if (y === x) { +//// console.log("goodbye"); +//// console.log("world"); +//// } +//// +// @Filename: /index.ts +//// function foo() { +//// const x: number = 1; +//// const y: number = 2; +//// if (x === y) [||]{ +//// console.log("hello"); +//// console.log("you"); +//// } +//// return 1; +//// } +//// + +verify.baselineMapCode(test.ranges()[0]); \ No newline at end of file diff --git a/tests/cases/fourslash/mapCodeNestedIfReplace.ts b/tests/cases/fourslash/mapCodeNestedIfReplace.ts new file mode 100644 index 0000000000000..e07bee4e83949 --- /dev/null +++ b/tests/cases/fourslash/mapCodeNestedIfReplace.ts @@ -0,0 +1,21 @@ +/// + +// @Filename: /incomingChanges +//// if (x === y) { +//// console.log("goodbye"); +//// console.log("world"); +//// } +//// +// @Filename: /index.ts +//// function foo() { +//// const x: number = 1; +//// const y: number = 2; +//// if (x === y) [||]{ +//// console.log("hello"); +//// console.log("you"); +//// } +//// return 1; +//// } +//// + +verify.baselineMapCode(test.ranges()[0]); \ No newline at end of file diff --git a/tests/cases/fourslash/mapCodeNestedLabeledInsertion.ts b/tests/cases/fourslash/mapCodeNestedLabeledInsertion.ts new file mode 100644 index 0000000000000..f84713a335474 --- /dev/null +++ b/tests/cases/fourslash/mapCodeNestedLabeledInsertion.ts @@ -0,0 +1,23 @@ +/// + +// @Filename: /incomingChanges +//// otherLabel: if (y === x) { +//// console.log("goodbye"); +//// console.log("world"); +//// break otherLabel; +//// } +//// +// @Filename: /index.ts +//// function foo() { +//// const x: number = 1; +//// const y: number = 2; +//// myLabel: if (x === y) [||]{ +//// console.log("hello"); +//// console.log("you"); +//// break myLabel; +//// } +//// return 1; +//// } +//// + +verify.baselineMapCode(test.ranges()[0]); \ No newline at end of file diff --git a/tests/cases/fourslash/mapCodeNestedLabeledReplace.ts b/tests/cases/fourslash/mapCodeNestedLabeledReplace.ts new file mode 100644 index 0000000000000..cf2b5c6a31aeb --- /dev/null +++ b/tests/cases/fourslash/mapCodeNestedLabeledReplace.ts @@ -0,0 +1,23 @@ +/// + +// @Filename: /incomingChanges +//// myLabel: if (y === x) { +//// console.log("goodbye"); +//// console.log("world"); +//// break myLabel; +//// } +//// +// @Filename: /index.ts +//// function foo() { +//// const x: number = 1; +//// const y: number = 2; +//// myLabel: if (x === y) [||]{ +//// console.log("hello"); +//// console.log("you"); +//// break myLabel; +//// } +//// return 1; +//// } +//// + +verify.baselineMapCode(test.ranges()[0]); \ No newline at end of file diff --git a/tests/cases/fourslash/mapCodeNestedWhileInsertion.ts b/tests/cases/fourslash/mapCodeNestedWhileInsertion.ts new file mode 100644 index 0000000000000..f5e89f59cd3b4 --- /dev/null +++ b/tests/cases/fourslash/mapCodeNestedWhileInsertion.ts @@ -0,0 +1,21 @@ +/// + +// @Filename: /incomingChanges +//// while (y === x) { +//// console.log("goodbye"); +//// console.log("world"); +//// } +//// +// @Filename: /index.ts +//// function foo() { +//// const x: number = 1; +//// const y: number = 2; +//// while (x === y) [||]{ +//// console.log("hello"); +//// console.log("you"); +//// } +//// return 1; +//// } +//// + +verify.baselineMapCode(test.ranges()[0]); \ No newline at end of file diff --git a/tests/cases/fourslash/mapCodeNestedWhileReplace.ts b/tests/cases/fourslash/mapCodeNestedWhileReplace.ts new file mode 100644 index 0000000000000..058ea43d417f4 --- /dev/null +++ b/tests/cases/fourslash/mapCodeNestedWhileReplace.ts @@ -0,0 +1,21 @@ +/// + +// @Filename: /incomingChanges +//// while (x === y) { +//// console.log("goodbye"); +//// console.log("world"); +//// } +//// +// @Filename: /index.ts +//// function foo() { +//// const x: number = 1; +//// const y: number = 2; +//// while (x === y) [||]{ +//// console.log("hello"); +//// console.log("you"); +//// } +//// return 1; +//// } +//// + +verify.baselineMapCode(test.ranges()[0]); \ No newline at end of file diff --git a/tests/cases/fourslash/mapCodeRangeReplacement.ts b/tests/cases/fourslash/mapCodeRangeReplacement.ts new file mode 100644 index 0000000000000..8510d5f08dc7e --- /dev/null +++ b/tests/cases/fourslash/mapCodeRangeReplacement.ts @@ -0,0 +1,32 @@ +/// + +// @Filename: /incomingChanges +//// if (foo == bar) { +//// console.log("huh"); +//// } +//// +//// function baz() { +//// return 'baz'; +//// } +//// +// @Filename: /index.ts +//// function foo() { +//// return 1; +//// } +//// if (foo == bar) { +//// console.log("hello"); +//// console.log("you"); +//// } +//// [||] +//// function bar() { +//// return 2; +//// } +//// function baz() { +//// return 3; +//// } +//// while (false) { +//// console.log("weee"); +//// } +//// + +verify.baselineMapCode(test.ranges()[0]); \ No newline at end of file diff --git a/tests/cases/fourslash/mapCodeToplevelInsert.ts b/tests/cases/fourslash/mapCodeToplevelInsert.ts new file mode 100644 index 0000000000000..cf542ef07e0ae --- /dev/null +++ b/tests/cases/fourslash/mapCodeToplevelInsert.ts @@ -0,0 +1,16 @@ +/// + +// @Filename: /incomingChanges +//// function baz() { +//// return 3; +//// } +//// +// @Filename: /index.ts +//// function foo() { +//// return 1; +//// }[||] +//// function bar() { +//// return 2; +//// } + +verify.baselineMapCode(test.ranges()[0]); \ No newline at end of file diff --git a/tests/cases/fourslash/mapCodeToplevelReplace.ts b/tests/cases/fourslash/mapCodeToplevelReplace.ts new file mode 100644 index 0000000000000..9ed1aef21c29f --- /dev/null +++ b/tests/cases/fourslash/mapCodeToplevelReplace.ts @@ -0,0 +1,17 @@ +/// + +// @Filename: /incomingChanges +//// function foo() { +//// return 3; +//// } +//// +// @Filename: /index.ts +//// function foo() { +//// return 1; +//// }[||] +//// function bar() { +//// return 2; +//// } +//// + +verify.baselineMapCode(test.ranges()[0]); \ No newline at end of file