Merge branch 'master' into allow-booleans-in-spreads

This commit is contained in:
Nathan Shively-Sanders
2017-09-14 10:30:58 -07:00
28 changed files with 384 additions and 91 deletions
+15 -1
View File
@@ -14793,7 +14793,7 @@ namespace ts {
return;
}
if (findAncestor(node, node => node.kind === SyntaxKind.PropertyDeclaration ? true : isExpression(node) ? false : "quit") &&
if (isInPropertyInitializer(node) &&
!isBlockScopedNameDeclaredBeforeUse(valueDeclaration, right)
&& !isPropertyDeclaredInAncestorClass(prop)) {
error(right, Diagnostics.Block_scoped_variable_0_used_before_its_declaration, unescapeLeadingUnderscores(right.escapedText));
@@ -14806,6 +14806,20 @@ namespace ts {
}
}
function isInPropertyInitializer(node: Node): boolean {
return !!findAncestor(node, node => {
switch (node.kind) {
case SyntaxKind.PropertyDeclaration:
return true;
case SyntaxKind.PropertyAssignment:
// We might be in `a = { b: this.b }`, so keep looking. See `tests/cases/compiler/useBeforeDeclaration_propertyAssignment.ts`.
return false;
default:
return isPartOfExpression(node) ? false : "quit";
}
});
}
/**
* It's possible that "prop.valueDeclaration" is a local declaration, but the property was also declared in a superclass.
* In that case we won't consider it used before its declaration, because it gets its value from the superclass' declaration.
+46 -47
View File
@@ -22,10 +22,6 @@
namespace FourSlash {
ts.disableIncrementalParsing = false;
function normalizeNewLines(s: string) {
return s.replace(/\r\n/g, "\n");
}
// Represents a parsed source file with metadata
export interface FourSlashFile {
// The contents of the file (with markers, etc stripped out)
@@ -364,7 +360,7 @@ namespace FourSlash {
baseIndentSize: 0,
indentSize: 4,
tabSize: 4,
newLineCharacter: Harness.IO.newLine(),
newLineCharacter: "\n",
convertTabsToSpaces: true,
indentStyle: ts.IndentStyle.Smart,
insertSpaceAfterCommaDelimiter: true,
@@ -1603,7 +1599,7 @@ namespace FourSlash {
}
}
public printCurrentFileState(makeWhitespaceVisible: boolean, makeCaretVisible: boolean) {
public printCurrentFileState(showWhitespace: boolean, makeCaretVisible: boolean) {
for (const file of this.testData.files) {
const active = (this.activeFile === file);
Harness.IO.log(`=== Script (${file.fileName}) ${(active ? "(active, cursor at |)" : "")} ===`);
@@ -1611,8 +1607,8 @@ namespace FourSlash {
if (active) {
content = content.substr(0, this.currentCaretPosition) + (makeCaretVisible ? "|" : "") + content.substr(this.currentCaretPosition);
}
if (makeWhitespaceVisible) {
content = TestState.makeWhitespaceVisible(content);
if (showWhitespace) {
content = makeWhitespaceVisible(content);
}
Harness.IO.log(content);
}
@@ -2128,10 +2124,8 @@ namespace FourSlash {
public verifyCurrentFileContent(text: string) {
const actual = this.getFileContent(this.activeFile.fileName);
if (normalizeNewLines(actual) !== normalizeNewLines(text)) {
throw new Error("verifyCurrentFileContent\n" +
"\tExpected: \"" + TestState.makeWhitespaceVisible(text) + "\"\n" +
"\t Actual: \"" + TestState.makeWhitespaceVisible(actual) + "\"");
if (actual !== text) {
throw new Error(`verifyCurrentFileContent failed:\n${showTextDiff(text, actual)}`);
}
}
@@ -2305,11 +2299,11 @@ namespace FourSlash {
const actualText = this.rangeText(ranges[0]);
const result = includeWhiteSpace
? normalizeNewLines(actualText) === normalizeNewLines(expectedText)
? actualText === expectedText
: this.removeWhitespace(actualText) === this.removeWhitespace(expectedText);
if (!result) {
this.raiseError(`Actual text doesn't match expected text. Actual:\n'${actualText}'\nExpected:\n'${expectedText}'`);
this.raiseError(`Actual range text doesn't match expected text.\n${showTextDiff(expectedText, actualText)}`);
}
}
@@ -2403,15 +2397,19 @@ namespace FourSlash {
const originalContent = scriptInfo.content;
for (const codeFix of codeFixes) {
this.applyEdits(codeFix.changes[0].fileName, codeFix.changes[0].textChanges, /*isFormattingEdit*/ false);
actualTextArray.push(this.normalizeNewlines(this.rangeText(ranges[0])));
let text = this.rangeText(ranges[0]);
// TODO:GH#18445 (remove this line to see errors in many `importNameCodeFix` tests)
text = text.replace(/\r\n/g, "\n");
actualTextArray.push(text);
scriptInfo.updateContent(originalContent);
}
const sortedExpectedArray = ts.map(expectedTextArray, str => this.normalizeNewlines(str)).sort();
const sortedExpectedArray = expectedTextArray.sort();
const sortedActualArray = actualTextArray.sort();
if (!ts.arrayIsEqualTo(sortedExpectedArray, sortedActualArray)) {
this.raiseError(
`Actual text array doesn't match expected text array. \nActual: \n'${sortedActualArray.join("\n\n")}'\n---\nExpected: \n'${sortedExpectedArray.join("\n\n")}'`);
}
ts.zipWith(sortedExpectedArray, sortedActualArray, (expected, actual, index) => {
if (expected !== actual) {
this.raiseError(`Import fix at index ${index} doesn't match.\n${showTextDiff(expected, actual)}`);
}
});
}
public verifyDocCommentTemplate(expected: ts.TextInsertion | undefined) {
@@ -2431,7 +2429,7 @@ namespace FourSlash {
}
if (actual.newText !== expected.newText) {
this.raiseError(`${name} failed - expected insertion:\n"${this.clarifyNewlines(expected.newText)}"\nactual insertion:\n"${this.clarifyNewlines(actual.newText)}"`);
this.raiseError(`${name} failed for expected insertion.\n${showTextDiff(expected.newText, actual.newText)}`);
}
if (actual.caretOffset !== expected.caretOffset) {
@@ -2440,17 +2438,6 @@ namespace FourSlash {
}
}
private clarifyNewlines(str: string) {
return str.replace(/\r?\n/g, lineEnding => {
const representation = lineEnding === "\r\n" ? "CRLF" : "LF";
return "# - " + representation + lineEnding;
});
}
private normalizeNewlines(str: string) {
return str.replace(/\r?\n/g, "\n");
}
public verifyBraceCompletionAtPosition(negative: boolean, openingBrace: string) {
const openBraceMap = ts.createMapFromTemplate<ts.CharacterCodes>({
@@ -2878,8 +2865,8 @@ namespace FourSlash {
}
const actualContent = this.getFileContent(this.activeFile.fileName);
if (this.normalizeNewlines(actualContent) !== this.normalizeNewlines(expectedContent)) {
this.raiseError(`verifyFileAfterApplyingRefactors failed: expected:\n${expectedContent}\nactual:\n${actualContent}`);
if (actualContent !== expectedContent) {
this.raiseError(`verifyFileAfterApplyingRefactors failed:\n${showTextDiff(expectedContent, actualContent)}`);
}
}
@@ -3014,10 +3001,6 @@ namespace FourSlash {
}
}
private static makeWhitespaceVisible(text: string) {
return text.replace(/ /g, "\u00B7").replace(/\r/g, "\u00B6").replace(/\n/g, "\u2193\n").replace(/\t/g, "\u2192\ ");
}
public setCancelled(numberOfCalls: number): void {
this.cancellationToken.setCancelled(numberOfCalls);
}
@@ -3319,12 +3302,7 @@ ${code}
let column = 1;
const flush = (lastSafeCharIndex: number) => {
if (lastSafeCharIndex === undefined) {
output = output + content.substr(lastNormalCharPosition);
}
else {
output = output + content.substr(lastNormalCharPosition, lastSafeCharIndex - lastNormalCharPosition);
}
output = output + content.substr(lastNormalCharPosition, lastSafeCharIndex === undefined ? undefined : lastSafeCharIndex - lastNormalCharPosition);
};
if (content.length > 0) {
@@ -3511,6 +3489,27 @@ ${code}
function toArray<T>(x: T | T[]): T[] {
return ts.isArray(x) ? x : [x];
}
function makeWhitespaceVisible(text: string) {
return text.replace(/ /g, "\u00B7").replace(/\r/g, "\u00B6").replace(/\n/g, "\u2193\n").replace(/\t/g, "\u2192\ ");
}
function showTextDiff(expected: string, actual: string): string {
// Only show whitespace if the difference is whitespace-only.
if (differOnlyByWhitespace(expected, actual)) {
expected = makeWhitespaceVisible(expected);
actual = makeWhitespaceVisible(actual);
}
return `Expected:\n${expected}\nActual:${actual}`;
}
function differOnlyByWhitespace(a: string, b: string) {
return stripWhitespace(a) === stripWhitespace(b);
}
function stripWhitespace(s: string): string {
return s.replace(/\s/g, "");
}
}
namespace FourSlashInterface {
@@ -4143,15 +4142,15 @@ namespace FourSlashInterface {
}
public printCurrentFileState() {
this.state.printCurrentFileState(/*makeWhitespaceVisible*/ false, /*makeCaretVisible*/ true);
this.state.printCurrentFileState(/*showWhitespace*/ false, /*makeCaretVisible*/ true);
}
public printCurrentFileStateWithWhitespace() {
this.state.printCurrentFileState(/*makeWhitespaceVisible*/ true, /*makeCaretVisible*/ true);
this.state.printCurrentFileState(/*showWhitespace*/ true, /*makeCaretVisible*/ true);
}
public printCurrentFileStateWithoutCaret() {
this.state.printCurrentFileState(/*makeWhitespaceVisible*/ false, /*makeCaretVisible*/ false);
this.state.printCurrentFileState(/*showWhitespace*/ false, /*makeCaretVisible*/ false);
}
public printCurrentQuickInfo() {
+2 -1
View File
@@ -500,7 +500,8 @@ namespace Harness {
export let IO: IO;
// harness always uses one kind of new line
const harnessNewLine = "\r\n";
// But note that `parseTestData` in `fourslash.ts` uses "\n"
export const harnessNewLine = "\r\n";
// Root for file paths that are stored in a virtual file system
export const virtualFileSystemRoot = "/";
+1 -1
View File
@@ -130,7 +130,7 @@ namespace Harness.LanguageService {
}
public getNewLine(): string {
return "\r\n";
return harnessNewLine;
}
public getFilenames(): string[] {
+2 -2
View File
@@ -216,8 +216,8 @@ if (taskConfigsFolder) {
for (let i = 0; i < workerCount; i++) {
const config = workerConfigs[i];
// use last worker to run unit tests
config.runUnitTests = i === workerCount - 1;
// use last worker to run unit tests if we're not just running a single specific runner
config.runUnitTests = runners.length !== 1 && i === workerCount - 1;
Harness.IO.writeFile(ts.combinePaths(taskConfigsFolder, `task-config${i}.json`), JSON.stringify(workerConfigs[i]));
}
}
+1 -1
View File
@@ -263,7 +263,7 @@ class RWCRunner extends RunnerBase {
*/
public initializeTests(): void {
// Read in and evaluate the test list
const testList = this.enumerateTestFiles();
const testList = this.tests && this.tests.length ? this.tests : this.enumerateTestFiles();
for (let i = 0; i < testList.length; i++) {
this.runTest(testList[i]);
}
+57
View File
@@ -404,6 +404,12 @@ function test(x: number) {
"Cannot extract range containing conditional break or continue statements."
]);
testExtractRangeFailed("extractRangeFailed9",
`var x = ([#||]1 + 2);`,
[
"Statement or expression expected."
]);
testExtractMethod("extractMethod1",
`namespace A {
let x = 1;
@@ -709,6 +715,57 @@ function M3() { }`);
}
M3() { }
constructor() { }
}`);
// Shorthand property names
testExtractMethod("extractMethod29",
`interface UnaryExpression {
kind: "Unary";
operator: string;
operand: any;
}
function parseUnaryExpression(operator: string): UnaryExpression {
[#|return {
kind: "Unary",
operator,
operand: parsePrimaryExpression(),
};|]
}
function parsePrimaryExpression(): any {
throw "Not implemented";
}`);
// Type parameter as declared type
testExtractMethod("extractMethod30",
`function F<T>() {
[#|let t: T;|]
}`);
// Return in nested function
testExtractMethod("extractMethod31",
`namespace N {
export const value = 1;
() => {
var f: () => number;
[#|f = function (): number {
return value;
}|]
}
}`);
// Return in nested class
testExtractMethod("extractMethod32",
`namespace N {
export const value = 1;
() => {
[#|var c = class {
M() {
return value;
}
}|]
}
}`);
});
+2 -1
View File
@@ -573,7 +573,7 @@ namespace ts.server {
getEditsForRefactor(
fileName: string,
_formatOptions: FormatCodeSettings,
formatOptions: FormatCodeSettings,
positionOrRange: number | TextRange,
refactorName: string,
actionName: string): RefactorEditInfo {
@@ -581,6 +581,7 @@ namespace ts.server {
const args = this.createFileLocationOrRangeRequestArgs(positionOrRange, fileName) as protocol.GetEditsForRefactorRequestArgs;
args.refactor = refactorName;
args.action = actionName;
args.formatOptions = formatOptions;
const request = this.processRequest<protocol.GetEditsForRefactorRequest>(CommandNames.GetEditsForRefactor, args);
const response = this.processResponse<protocol.GetEditsForRefactorResponse>(request);
+1
View File
@@ -494,6 +494,7 @@ namespace ts.server.protocol {
refactor: string;
/* The 'name' property from the refactoring action */
action: string;
formatOptions: FormatCodeSettings,
};
+1 -1
View File
@@ -1488,7 +1488,7 @@ namespace ts.server {
const result = project.getLanguageService().getEditsForRefactor(
file,
this.projectService.getFormatCodeOptions(),
convertFormatOptions(args.formatOptions),
position || textRange,
args.refactor,
args.action
+22 -4
View File
@@ -149,6 +149,11 @@ namespace ts.refactor.extractMethod {
// exported only for tests
export function getRangeToExtract(sourceFile: SourceFile, span: TextSpan): RangeToExtract {
const length = span.length || 0;
if (length === 0) {
return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.StatementOrExpressionExpected)] };
}
// Walk up starting from the the start position until we find a non-SourceFile node that subsumes the selected span.
// This may fail (e.g. you select two statements in the root of a source file)
let start = getParentNodeInSpan(getTokenAtPosition(sourceFile, span.start, /*includeJsDocComment*/ false), sourceFile, span);
@@ -855,6 +860,7 @@ namespace ts.refactor.extractMethod {
return { body: createBlock(body.statements, /*multLine*/ true), returnValueProperty: undefined };
}
let returnValueProperty: string;
let ignoreReturns = false;
const statements = createNodeArray(isBlock(body) ? body.statements.slice(0) : [isStatement(body) ? body : createReturn(<Expression>body)]);
// rewrite body if either there are writes that should be propagated back via return statements or there are substitutions
if (writes || substitutions.size) {
@@ -877,7 +883,7 @@ namespace ts.refactor.extractMethod {
}
function visitor(node: Node): VisitResult<Node> {
if (node.kind === SyntaxKind.ReturnStatement && writes) {
if (!ignoreReturns && node.kind === SyntaxKind.ReturnStatement && writes) {
const assignments: ObjectLiteralElementLike[] = getPropertyAssignmentsForWrites(writes);
if ((<ReturnStatement>node).expression) {
if (!returnValueProperty) {
@@ -893,8 +899,12 @@ namespace ts.refactor.extractMethod {
}
}
else {
const oldIgnoreReturns = ignoreReturns;
ignoreReturns = ignoreReturns || isFunctionLike(node) || isClassLike(node);
const substitution = substitutions.get(getNodeId(node).toString());
return substitution || visitEachChild(node, visitor, nullTransformationContext);
const result = substitution || visitEachChild(node, visitor, nullTransformationContext);
ignoreReturns = oldIgnoreReturns;
return result;
}
}
}
@@ -1161,7 +1171,11 @@ namespace ts.refactor.extractMethod {
}
function recordUsagebySymbol(identifier: Identifier, usage: Usage, isTypeName: boolean) {
const symbol = checker.getSymbolAtLocation(identifier);
// If the identifier is both a property name and its value, we're only interested in its value
// (since the name is a declaration and will be included in the extracted range).
const symbol = identifier.parent && isShorthandPropertyAssignment(identifier.parent) && identifier.parent.name === identifier
? checker.getShorthandAssignmentValueSymbol(identifier.parent)
: checker.getSymbolAtLocation(identifier);
if (!symbol) {
// cannot find symbol - do nothing
return undefined;
@@ -1218,7 +1232,11 @@ namespace ts.refactor.extractMethod {
substitutionsPerScope[i].set(symbolId, substitution);
}
else if (isTypeName) {
errorsPerScope[i].push(createDiagnosticForNode(identifier, Messages.TypeWillNotBeVisibleInTheNewScope));
// If the symbol is a type parameter that won't be in scope, we'll pass it as a type argument
// so there's no problem.
if (!(symbol.flags & SymbolFlags.TypeParameter)) {
errorsPerScope[i].push(createDiagnosticForNode(identifier, Messages.TypeWillNotBeVisibleInTheNewScope));
}
}
else {
usagesPerScope[i].usages.set(identifier.text as string, { usage, symbol, node: identifier });
+1 -1
View File
@@ -2019,7 +2019,7 @@ namespace ts {
startPosition,
endPosition,
program: getProgram(),
newLineCharacter: host.getNewLine(),
newLineCharacter: formatOptions ? formatOptions.newLineCharacter : host.getNewLine(),
rulesProvider: getRuleProvider(formatOptions),
cancellationToken
};
+1 -5
View File
@@ -184,16 +184,12 @@ namespace ts.textChanges {
return s;
}
function getNewlineKind(context: { newLineCharacter: string }) {
return context.newLineCharacter === "\n" ? NewLineKind.LineFeed : NewLineKind.CarriageReturnLineFeed;
}
export class ChangeTracker {
private changes: Change[] = [];
private readonly newLineCharacter: string;
public static fromContext(context: RefactorContext | CodeFixContext) {
return new ChangeTracker(getNewlineKind(context), context.rulesProvider);
return new ChangeTracker(context.newLineCharacter === "\n" ? NewLineKind.LineFeed : NewLineKind.CarriageReturnLineFeed, context.rulesProvider);
}
constructor(
@@ -0,0 +1,62 @@
// ==ORIGINAL==
interface UnaryExpression {
kind: "Unary";
operator: string;
operand: any;
}
function parseUnaryExpression(operator: string): UnaryExpression {
return {
kind: "Unary",
operator,
operand: parsePrimaryExpression(),
};
}
function parsePrimaryExpression(): any {
throw "Not implemented";
}
// ==SCOPE::inner function in function 'parseUnaryExpression'==
interface UnaryExpression {
kind: "Unary";
operator: string;
operand: any;
}
function parseUnaryExpression(operator: string): UnaryExpression {
return /*RENAME*/newFunction();
function newFunction() {
return {
kind: "Unary",
operator,
operand: parsePrimaryExpression(),
};
}
}
function parsePrimaryExpression(): any {
throw "Not implemented";
}
// ==SCOPE::function in global scope==
interface UnaryExpression {
kind: "Unary";
operator: string;
operand: any;
}
function parseUnaryExpression(operator: string): UnaryExpression {
return /*RENAME*/newFunction(operator);
}
function newFunction(operator: string) {
return {
kind: "Unary",
operator,
operand: parsePrimaryExpression(),
};
}
function parsePrimaryExpression(): any {
throw "Not implemented";
}
@@ -0,0 +1,19 @@
// ==ORIGINAL==
function F<T>() {
let t: T;
}
// ==SCOPE::inner function in function 'F'==
function F<T>() {
/*RENAME*/newFunction();
function newFunction() {
let t: T;
}
}
// ==SCOPE::function in global scope==
function F<T>() {
/*RENAME*/newFunction<T>();
}
function newFunction<T>() {
let t: T;
}
@@ -0,0 +1,45 @@
// ==ORIGINAL==
namespace N {
export const value = 1;
() => {
var f: () => number;
f = function (): number {
return value;
}
}
}
// ==SCOPE::function in namespace 'N'==
namespace N {
export const value = 1;
() => {
var f: () => number;
f = /*RENAME*/newFunction(f);
}
function newFunction(f: () => number) {
f = function(): number {
return value;
};
return f;
}
}
// ==SCOPE::function in global scope==
namespace N {
export const value = 1;
() => {
var f: () => number;
f = /*RENAME*/newFunction(f);
}
}
function newFunction(f: () => number) {
f = function(): number {
return N.value;
};
return f;
}
@@ -0,0 +1,46 @@
// ==ORIGINAL==
namespace N {
export const value = 1;
() => {
var c = class {
M() {
return value;
}
}
}
}
// ==SCOPE::function in namespace 'N'==
namespace N {
export const value = 1;
() => {
/*RENAME*/newFunction();
}
function newFunction() {
var c = class {
M() {
return value;
}
};
}
}
// ==SCOPE::function in global scope==
namespace N {
export const value = 1;
() => {
/*RENAME*/newFunction();
}
}
function newFunction() {
var c = class {
M() {
return N.value;
}
};
}
@@ -0,0 +1,11 @@
tests/cases/compiler/useBeforeDeclaration_propertyAssignment.ts(2,27): error TS2448: Block-scoped variable 'b' used before its declaration.
==== tests/cases/compiler/useBeforeDeclaration_propertyAssignment.ts (1 errors) ====
export class C {
public a = { b: this.b };
~
!!! error TS2448: Block-scoped variable 'b' used before its declaration.
private b = 0;
}
@@ -0,0 +1,18 @@
//// [useBeforeDeclaration_propertyAssignment.ts]
export class C {
public a = { b: this.b };
private b = 0;
}
//// [useBeforeDeclaration_propertyAssignment.js]
"use strict";
exports.__esModule = true;
var C = /** @class */ (function () {
function C() {
this.a = { b: this.b };
this.b = 0;
}
return C;
}());
exports.C = C;
@@ -0,0 +1,4 @@
export class C {
public a = { b: this.b };
private b = 0;
}
@@ -17,5 +17,4 @@ verify.currentFileContentIs(`class C {
()=>{ this.foo === 10 };
}
}
C.foo = undefined;
`);
C.foo = undefined;` + "\r\n"); // TODO: GH#18445
@@ -13,5 +13,4 @@ verify.getAndApplyCodeFix(/*errorCode*/ undefined, /*index*/ 2)
verify.currentFileContentIs(`class C {
static p = ()=>{ this.foo === 10 };
}
C.foo = undefined;
`);
C.foo = undefined;` + "\r\n"); // TODO: GH#18445
@@ -9,7 +9,8 @@
//// super();
//// |]}
////}
// TODO: GH#18445
verify.rangeAfterCodeFix(`
super();
super();\r
this.a = 12;
`, /*includeWhiteSpace*/ true);
`, /*includeWhiteSpace*/ true);
+2 -1
View File
@@ -6,6 +6,7 @@
//// constructor() {[|
//// |]}
////}
// TODO: GH#18445
verify.rangeAfterCodeFix(`
super();
super();\r
`, /*includeWhitespace*/ true);
+1 -1
View File
@@ -19,7 +19,7 @@ edit.applyRefactor({
`function foo() {
var i = 10;
var __return: any;
({ __return, i } = n/*RENAME*/ewFunction(i));
({ __return, i } = /*RENAME*/newFunction(i));
return __return;
}
function newFunction(i) {
@@ -11,12 +11,12 @@
////}
format.document();
verify.currentFileContentIs("class C {\r\n\
<<<<<<< HEAD\r\n\
v = 1;\r\n\
||||||| merged common ancestors\r\n\
v = 3;\r\n\
=======\r\n\
v = 2;\r\n\
>>>>>>> Branch - a\r\n\
}");
verify.currentFileContentIs(`class C {
<<<<<<< HEAD
v = 1;
||||||| merged common ancestors
v = 3;
=======
v = 2;
>>>>>>> Branch - a
}`);
@@ -9,10 +9,10 @@
////}
format.document();
verify.currentFileContentIs("class C {\r\n\
<<<<<<< HEAD\r\n\
v = 1;\r\n\
=======\r\n\
v = 2;\r\n\
>>>>>>> Branch - a\r\n\
}");
verify.currentFileContentIs(`class C {
<<<<<<< HEAD
v = 1;
=======
v = 2;
>>>>>>> Branch - a
}`);
@@ -10,7 +10,8 @@
////x;|]
goTo.file("/b.ts");
verify.rangeAfterCodeFix(`import { x } from "./a";
// TODO:GH#18445
verify.rangeAfterCodeFix(`import { x } from "./a";\r
\r
export { x } from "./a";
x;`, /*includeWhiteSpace*/ true);