CodeMapper support (#55406)

This commit is contained in:
Kat Marchán
2024-05-22 16:47:08 -07:00
committed by GitHub
parent 8537bb72eb
commit ddf43cd0e0
64 changed files with 1931 additions and 0 deletions
+2
View File
@@ -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)
+51
View File
@@ -4510,6 +4510,57 @@ export class TestState {
this.verifyCurrentFileContent(newFileContent);
}
public baselineMapCode(
ranges: Range[][],
changes: string[] = [],
): void {
const fileName = this.activeFile.fileName;
const focusLocations = ranges.map(r =>
r.map(({ pos, end }) => {
return { start: pos, length: end - pos };
})
);
let before = this.getFileContent(fileName);
const edits = this.languageService.mapCode(
fileName,
// We trim the leading whitespace stuff just so our test cases can be more readable.
changes,
focusLocations,
this.formatCodeSettings,
{},
);
this.applyChanges(edits);
focusLocations.forEach(r => {
r.sort((a, b) => a.start - b.start);
});
focusLocations.sort((a, b) => a[0].start - b[0].start);
for (const subLoc of focusLocations) {
for (const { start, length } of subLoc) {
let offset = 0;
for (const sl2 of focusLocations) {
for (const { start: s2, length: l2 } of sl2) {
if (s2 < start) {
offset += 4;
if ((s2 + l2) > start) {
offset -= 2;
}
}
}
}
before = before.slice(0, start + offset) + "[|" + before.slice(start + offset, start + offset + length) + "|]" + before.slice(start + offset + length);
}
}
const after = this.getFileContent(fileName);
const baseline = `
// === ORIGINAL ===
${before}
// === INCOMING CHANGES ===
${changes.join("\n// ---\n")}
// === MAPPED ===
${after}`;
this.baseline("mapCode", baseline, ".mapCode.ts");
}
}
function updateTextRangeForTextChanges({ pos, end }: ts.TextRange, textChanges: readonly ts.TextChange[]): ts.TextRange {
+4
View File
@@ -239,6 +239,10 @@ export class VerifyNegatable {
public uncommentSelection(newFileContent: string) {
this.state.uncommentSelection(newFileContent);
}
public baselineMapCode(ranges: FourSlash.Range[][], changes: string[] = []): void {
this.state.baselineMapCode(ranges, changes);
}
}
export class Verify extends VerifyNegatable {
+33
View File
@@ -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.
*/
+23
View File
@@ -1906,6 +1906,26 @@ export class Session<TMessage = string> 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<TMessage = string> 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) {
+3
View File
@@ -0,0 +1,3 @@
/* Generated file to emulate the ts.MapCode namespace. */
export * from "../mapCode.js";
+2
View File
@@ -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 };
+323
View File
@@ -0,0 +1,323 @@
import {
Block,
ClassElement,
ClassLikeDeclaration,
createSourceFile,
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,
some,
SourceFile,
Statement,
SyntaxKind,
textChanges,
TextSpan,
TypeElement,
UserPreferences,
} from "./_namespaces/ts.js";
import { ChangeTracker } from "./textChanges.js";
/** @internal */
export function mapCode(
sourceFile: SourceFile,
contents: string[],
focusLocations: TextSpan[][] | undefined,
host: LanguageServiceHost,
formatContext: formatting.FormatContext,
preferences: UserPreferences,
): FileTextChanges[] {
return textChanges.ChangeTracker.with(
{ host, formatContext, preferences },
changeTracker => {
const parsed = contents.map(c => parse(sourceFile, c));
const flattenedLocations = focusLocations && flatten(focusLocations);
for (const nodes of parsed) {
placeNodeGroup(
sourceFile,
changeTracker,
nodes,
flattenedLocations,
);
}
},
);
}
/**
* Tries to parse something into either "top-level" statements, or into blocks
* of class-context definitions.
*/
function parse(sourceFile: SourceFile, content: string): NodeArray<Node> {
// 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,
sourceFile.languageVersion,
/*setParentNodes*/ true,
sourceFile.scriptKind,
),
body: (sf: SourceFile) => sf.statements,
},
{
parse: () =>
createSourceFile(
"__mapcode_class_content_nodes.ts",
`class __class {\n${content}\n}`,
sourceFile.languageVersion,
/*setParentNodes*/ true,
sourceFile.scriptKind,
),
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<Node>,
focusLocations?: TextSpan[],
) {
if (isClassElement(changes[0]) || isTypeElement(changes[0])) {
placeClassNodeGroup(
originalFile,
changeTracker,
changes as NodeArray<ClassElement>,
focusLocations,
);
}
else {
placeStatements(
originalFile,
changeTracker,
changes as NodeArray<Statement>,
focusLocations,
);
}
}
function placeClassNodeGroup(
originalFile: SourceFile,
changeTracker: ChangeTracker,
changes: NodeArray<ClassElement> | NodeArray<TypeElement>,
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) {
// No class? don't insert.
return;
}
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<ClassElement | TypeElement>,
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<Statement>,
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<Statement> = 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<Node>) {
resetNodePositions(node);
node.parent = undefined!;
}
function resetNodePositions(node: Mutable<Node>) {
node.pos = -1;
node.end = -1;
node.forEachChild(resetNodePositions);
}
+13
View File
@@ -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): 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) {
+2
View File
@@ -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): readonly FileTextChanges[];
dispose(): void;
getPasteEdits(
args: PasteEditsArgs,
+29
View File
@@ -121,6 +121,7 @@ declare namespace ts {
ProvideCallHierarchyOutgoingCalls = "provideCallHierarchyOutgoingCalls",
ProvideInlayHints = "provideInlayHints",
WatchChange = "watchChange",
MapCode = "mapCode",
}
/**
* A TypeScript Server message
@@ -1769,6 +1770,33 @@ declare namespace ts {
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.
*/
@@ -3468,6 +3496,7 @@ declare namespace ts {
private getLinkedEditingRange;
private getDocumentHighlights;
private provideInlayHints;
private mapCode;
private setCompilerOptionsForInferredProjects;
private getProjectInfo;
private getProjectInfoWorker;
@@ -0,0 +1,19 @@
// === mapCode ===
// === ORIGINAL ===
class MyClass {[||]
}
// === INCOMING CHANGES ===
if (false) {
return "hello";
}
// === MAPPED ===
class MyClass {
}
if (false) {
return "hello";
}
@@ -0,0 +1,64 @@
// === 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;
}
@@ -0,0 +1,38 @@
// === 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;
}
}
@@ -0,0 +1,35 @@
// === 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;
}
}
@@ -0,0 +1,35 @@
// === 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;
}
@@ -0,0 +1,52 @@
// === mapCode ===
// === ORIGINAL ===
[|function foo() {
const x: number = 1;
const y: number = 2;
if (x === y) [||]{
console.log("hello");
console.log("you");
}
return 1;
}|]
function bar() {
[|return 2|];
}
// === INCOMING CHANGES ===
if (x === y) {
console.log("goodbye");
console.log("world");
}
// ---
function bar() {
return 3;
}
// ---
method() {
return 'nope';
}
// === MAPPED ===
function foo() {
const x: number = 1;
const y: number = 2;
if (x === y) {
console.log("goodbye");
console.log("world");
}
return 1;
}
function bar() {
return 3;
}
@@ -0,0 +1,42 @@
// === 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;
}
}
@@ -0,0 +1,39 @@
// === 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;
}
}
@@ -0,0 +1,34 @@
// === 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;
}
@@ -0,0 +1,30 @@
// === 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;
}
@@ -0,0 +1,26 @@
// === 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;
}
@@ -0,0 +1,26 @@
// === 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;
}
@@ -0,0 +1,34 @@
// === 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;
}
@@ -0,0 +1,30 @@
// === 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;
}
@@ -0,0 +1,38 @@
// === 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;
}
@@ -0,0 +1,33 @@
// === 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;
}
@@ -0,0 +1,34 @@
// === 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;
}
@@ -0,0 +1,30 @@
// === 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;
}
@@ -0,0 +1,27 @@
// === mapCode ===
// === ORIGINAL ===
function foo() {
return 1;
}
[||]
function bar() {
return 2;
}
function baz() {
return 3;
}
// === INCOMING CHANGES ===
// === MAPPED ===
function foo() {
return 1;
}
function bar() {
return 2;
}
function baz() {
return 3;
}
@@ -0,0 +1,27 @@
// === 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;
}
@@ -0,0 +1,25 @@
// === 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;
}
@@ -0,0 +1,44 @@
// === 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");
}
@@ -0,0 +1,23 @@
// === 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;
}
@@ -0,0 +1,25 @@
// === 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;
}
@@ -0,0 +1,22 @@
// === mapCode ===
// === ORIGINAL ===
function foo() {
return 1;
}[||]
function bar() {
return 2;
}
// === INCOMING CHANGES ===
baz() {
return 3;
}
// === MAPPED ===
function foo() {
return 1;
}
function bar() {
return 2;
}
@@ -0,0 +1,23 @@
// === 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;
}
+1
View File
@@ -452,6 +452,7 @@ declare namespace FourSlashInterface {
copiedFrom?: { file: string, range: { pos: number, end: number }[] };
}
}): void;
baselineMapCode(ranges: Range[][], changes: string[]): void;
}
class edit {
caretPosition(): Marker;
@@ -0,0 +1,14 @@
///<reference path="fourslash.ts"/>
// @Filename: /index.ts
//// class MyClass {[||]
//// }
////
verify.baselineMapCode([test.ranges()], [
`
if (false) {
return "hello";
}
`
]);
@@ -0,0 +1,39 @@
///<reference path="fourslash.ts"/>
// @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()], [
`
if (x === y) {
return x + y;
}
`
]);
@@ -0,0 +1,24 @@
///<reference path="fourslash.ts"/>
// @Filename: /index.ts
//// class MyClass {[||]
//// x = 1;
//// foo() {
//// return 1;
//// }
//// bar() {
//// return 2;
//// }
//// baz() {
//// return 3;
//// }
//// }
////
verify.baselineMapCode([test.ranges()], [
`
quux() {
return 4;
}
`
]);
@@ -0,0 +1,24 @@
///<reference path="fourslash.ts"/>
// @Filename: /index.ts
//// class MyClass {
//// x = 1;
//// foo() {
//// return 1;
//// }
//// bar() {[||]
//// return 2;
//// }
//// baz() {
//// return 3;
//// }
//// }
////
verify.baselineMapCode([test.ranges()], [
`
bar() {
return 'hello';
}
`
]);
@@ -0,0 +1,26 @@
///<reference path="fourslash.ts"/>
// @Filename: /index.ts
//// class MyClass {[||]
//// x = 1;
//// foo() {
//// return 1;
//// }
//// bar() {
//// return 2;
//// }
//// }
////
verify.baselineMapCode([test.ranges()], [
`
x = 3;
bar() {
return 'hello';
}
baz() {
return 3;
}
y = 2;
`
]);
@@ -0,0 +1,38 @@
///<reference path="fourslash.ts"/>
// @Filename: /index.ts
////
//// [|function foo() {
//// const x: number = 1;
//// const y: number = 2;
//// if (x === y) [||]{
//// console.log("hello");
//// console.log("you");
//// }
//// return 1;
//// }|]
////
//// function bar() {
//// [|return 2|];
//// }
////
const [r0, r1, r2] = test.ranges();
verify.baselineMapCode([[r0], [r2, r1]], [
`
if (x === y) {
console.log("goodbye");
console.log("world");
}
`,
`
function bar() {
return 3;
}
`,
`
method() {
return 'nope';
}
`
]);
@@ -0,0 +1,26 @@
///<reference path="fourslash.ts"/>
// @Filename: /index.ts
//// class MyClass {
//// x = 1;
//// foo() {
//// return 1;
//// }
//// bar() {
//// if (true)[||] {
//// return 2;
//// }
//// }
//// baz() {
//// return 3;
//// }
//// }
////
verify.baselineMapCode([test.ranges()], [
`
if (false) {
return "hello";
}
`
]);
@@ -0,0 +1,26 @@
///<reference path="fourslash.ts"/>
// @Filename: /index.ts
//// class MyClass {
//// x = 1;
//// foo() {
//// return 1;
//// }
//// bar() {
//// if (true) [||]{
//// return 2;
//// }
//// }
//// baz() {
//// return 3;
//// }
//// }
////
verify.baselineMapCode([test.ranges()], [
`
if (true) {
return "hello";
}
`
]);
@@ -0,0 +1,22 @@
///<reference path="fourslash.ts"/>
// @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()], [
`
for (let j = 0; j < 10; j++) {
console.log("goodbye");
console.log("world");
}
`
]);
@@ -0,0 +1,20 @@
///<reference path="fourslash.ts"/>
// @Filename: /index.ts
//// function foo() {
//// for (const x of [1, 2, 3])[||] {
//// console.log("hello");
//// console.log("you");
//// }
//// return 1;
//// }
////
verify.baselineMapCode([test.ranges()], [
`
for (const y of [1, 2, 3]) {
console.log("goodbye");
console.log("world");
}
`
]);
@@ -0,0 +1,20 @@
///<reference path="fourslash.ts"/>
// @Filename: /index.ts
//// function foo() {
//// for (const x of [1, 2, 3]) [||]{
//// console.log("hello");
//// console.log("you");
//// }
//// return 1;
//// }
////
verify.baselineMapCode([test.ranges()], [
`
for (const x of [1, 2, 3]) {
console.log("goodbye");
console.log("world");
}
`
]);
@@ -0,0 +1,20 @@
///<reference path="fourslash.ts"/>
// @Filename: /index.ts
//// function foo() {
//// for (let x = 0; x < 10; x++) [||]{
//// console.log("hello");
//// console.log("you");
//// }
//// return 1;
//// }
////
verify.baselineMapCode([test.ranges()], [
`
for (let x = 0; x < 10; x++) {
console.log("goodbye");
console.log("world");
}
`
]);
@@ -0,0 +1,22 @@
///<reference path="fourslash.ts"/>
// @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()], [
`
if (y === x) {
console.log("goodbye");
console.log("world");
}
`
]);
@@ -0,0 +1,22 @@
///<reference path="fourslash.ts"/>
// @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()], [
`
if (x === y) {
console.log("goodbye");
console.log("world");
}
`
]);
@@ -0,0 +1,24 @@
///<reference path="fourslash.ts"/>
// @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()], [
`
otherLabel: if (y === x) {
console.log("goodbye");
console.log("world");
break otherLabel;
}
`
]);
@@ -0,0 +1,24 @@
///<reference path="fourslash.ts"/>
// @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()], [
`
myLabel: if (y === x) {
console.log("goodbye");
console.log("world");
break myLabel;
}
`
]);
@@ -0,0 +1,22 @@
///<reference path="fourslash.ts"/>
// @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()], [
`
while (y === x) {
console.log("goodbye");
console.log("world");
}
`
]);
@@ -0,0 +1,22 @@
///<reference path="fourslash.ts"/>
// @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()], [
`
while (x === y) {
console.log("goodbye");
console.log("world");
}
`
]);
+16
View File
@@ -0,0 +1,16 @@
///<reference path="fourslash.ts"/>
// @Filename: /index.ts
//// function foo() {
//// return 1;
//// }
//// [||]
//// function bar() {
//// return 2;
//// }
//// function baz() {
//// return 3;
//// }
////
verify.baselineMapCode([test.ranges()], []);
+19
View File
@@ -0,0 +1,19 @@
///<reference path="fourslash.ts"/>
// @Filename: /index.ts
//// function foo() {
//// return 1;
//// }
//// function bar() {
//// return 2;
//// }
////
verify.baselineMapCode([[]], [
`
function baz() {
return 3;
}
`
]);
@@ -0,0 +1,17 @@
///<reference path="fourslash.ts"/>
// @Filename: /topLevelInsert.js
//// function foo() {
//// return 1;
//// }[||]
//// function bar() {
//// return 2;
//// }
verify.baselineMapCode([test.ranges()], [
`
function baz() {
return 3;
}
`
]);
@@ -0,0 +1,33 @@
///<reference path="fourslash.ts"/>
// @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()], [
`
if (foo == bar) {
console.log("huh");
}
function baz() {
return 'baz';
}
`
]);
@@ -0,0 +1,18 @@
///<reference path="fourslash.ts"/>
// @Filename: /index.ts
//// [|function foo() {
//// return 1;
//// }[||]
//// function bar() {
//// return [|2;|]
//// }|]
////
verify.baselineMapCode([test.ranges()], [
`
function foo() {
return 3;
}
`
]);
@@ -0,0 +1,17 @@
///<reference path="fourslash.ts"/>
// @Filename: /topLevelInsert.ts
//// function foo() {
//// return 1;
//// }[||]
//// function bar() {
//// return 2;
//// }
verify.baselineMapCode([test.ranges()], [
`
function baz() {
return 3;
}
`
]);
@@ -0,0 +1,17 @@
///<reference path="fourslash.ts"/>
// @Filename: /index.ts
//// function foo() {
//// return 1;
//// }[||]
//// function bar() {
//// return 2;
//// }
verify.baselineMapCode([test.ranges()], [
`
baz() {
return 3;
}
`
]);
@@ -0,0 +1,18 @@
///<reference path="fourslash.ts"/>
// @Filename: /topLevelReplace.ts
//// function foo() {
//// return 1;
//// }[||]
//// function bar() {
//// return 2;
//// }
////
verify.baselineMapCode([test.ranges()], [
`
function foo() {
return 3;
}
`
]);