diff --git a/Jakefile.js b/Jakefile.js
index 359549d8248..8a4c67ac84b 100644
--- a/Jakefile.js
+++ b/Jakefile.js
@@ -140,6 +140,8 @@ var harnessSources = harnessCoreSources.concat([
"initializeTSConfig.ts",
"extractConstants.ts",
"extractFunctions.ts",
+ "extractRanges.ts",
+ "extractTestHelpers.ts",
"printer.ts",
"textChanges.ts",
"telemetry.ts",
diff --git a/src/harness/tsconfig.json b/src/harness/tsconfig.json
index cb2cb00b861..88999b2d979 100644
--- a/src/harness/tsconfig.json
+++ b/src/harness/tsconfig.json
@@ -130,6 +130,8 @@
"./unittests/customTransforms.ts",
"./unittests/extractConstants.ts",
"./unittests/extractFunctions.ts",
+ "./unittests/extractRanges.ts",
+ "./unittests/extractTestHelpers.ts",
"./unittests/textChanges.ts",
"./unittests/telemetry.ts",
"./unittests/languageService.ts",
diff --git a/src/harness/unittests/extractConstants.ts b/src/harness/unittests/extractConstants.ts
index f5018811b1a..fb776eb6af3 100644
--- a/src/harness/unittests/extractConstants.ts
+++ b/src/harness/unittests/extractConstants.ts
@@ -1,104 +1,6 @@
-///
-///
+///
namespace ts {
- interface Range {
- start: number;
- end: number;
- name: string;
- }
-
- interface Test {
- source: string;
- ranges: Map;
- }
-
- // TODO (acasey): share
- function extractTest(source: string): Test {
- const activeRanges: Range[] = [];
- let text = "";
- let lastPos = 0;
- let pos = 0;
- const ranges = createMap();
-
- while (pos < source.length) {
- if (source.charCodeAt(pos) === CharacterCodes.openBracket &&
- (source.charCodeAt(pos + 1) === CharacterCodes.hash || source.charCodeAt(pos + 1) === CharacterCodes.$)) {
- const saved = pos;
- pos += 2;
- const s = pos;
- consumeIdentifier();
- const e = pos;
- if (source.charCodeAt(pos) === CharacterCodes.bar) {
- pos++;
- text += source.substring(lastPos, saved);
- const name = s === e
- ? source.charCodeAt(saved + 1) === CharacterCodes.hash ? "selection" : "extracted"
- : source.substring(s, e);
- activeRanges.push({ name, start: text.length, end: undefined });
- lastPos = pos;
- continue;
- }
- else {
- pos = saved;
- }
- }
- else if (source.charCodeAt(pos) === CharacterCodes.bar && source.charCodeAt(pos + 1) === CharacterCodes.closeBracket) {
- text += source.substring(lastPos, pos);
- activeRanges[activeRanges.length - 1].end = text.length;
- const range = activeRanges.pop();
- if (range.name in ranges) {
- throw new Error(`Duplicate name of range ${range.name}`);
- }
- ranges.set(range.name, range);
- pos += 2;
- lastPos = pos;
- continue;
- }
- pos++;
- }
- text += source.substring(lastPos, pos);
-
- function consumeIdentifier() {
- while (isIdentifierPart(source.charCodeAt(pos), ScriptTarget.Latest)) {
- pos++;
- }
- }
- return { source: text, ranges };
- }
-
- // TODO (acasey): share
- const newLineCharacter = "\n";
- function getRuleProvider(action?: (opts: FormatCodeSettings) => void) {
- const options = {
- indentSize: 4,
- tabSize: 4,
- newLineCharacter,
- convertTabsToSpaces: true,
- indentStyle: ts.IndentStyle.Smart,
- insertSpaceAfterConstructor: false,
- insertSpaceAfterCommaDelimiter: true,
- insertSpaceAfterSemicolonInForStatements: true,
- insertSpaceBeforeAndAfterBinaryOperators: true,
- insertSpaceAfterKeywordsInControlFlowStatements: true,
- insertSpaceAfterFunctionKeywordForAnonymousFunctions: false,
- insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
- insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false,
- insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
- insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false,
- insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false,
- insertSpaceBeforeFunctionParenthesis: false,
- placeOpenBraceOnNewLineForFunctions: false,
- placeOpenBraceOnNewLineForControlBlocks: false,
- };
- if (action) {
- action(options);
- }
- const rulesProvider = new formatting.RulesProvider();
- rulesProvider.ensureUpToDate(options);
- return rulesProvider;
- }
-
describe("extractConstants", () => {
testExtractConstant("extractConstant_TopLevel",
`let x = [#|1|];`);
@@ -168,83 +70,11 @@ namespace ts {
}`);
});
- // TODO (acasey): share?
function testExtractConstant(caption: string, text: string) {
- it(caption, () => {
- Harness.Baseline.runBaseline(`extractConstant/${caption}.ts`, () => {
- const t = extractTest(text);
- const selectionRange = t.ranges.get("selection");
- if (!selectionRange) {
- throw new Error(`Test ${caption} does not specify selection range`);
- }
- const f = {
- path: "/a.ts",
- content: t.source
- };
- const host = projectSystem.createServerHost([f, projectSystem.libFile]);
- const projectService = projectSystem.createProjectService(host);
- projectService.openClientFile(f.path);
- const program = projectService.inferredProjects[0].getLanguageService().getProgram();
- const sourceFile = program.getSourceFile(f.path);
- const context: RefactorContext = {
- cancellationToken: { throwIfCancellationRequested() { }, isCancellationRequested() { return false; } },
- newLineCharacter,
- program,
- file: sourceFile,
- startPosition: selectionRange.start,
- endPosition: selectionRange.end,
- rulesProvider: getRuleProvider()
- };
- const rangeToExtract = refactor.extractSymbol.getRangeToExtract(sourceFile, createTextSpanFromBounds(selectionRange.start, selectionRange.end));
- assert.equal(rangeToExtract.errors, undefined, rangeToExtract.errors && "Range error: " + rangeToExtract.errors[0].messageText);
- const infos = refactor.extractSymbol.getAvailableActions(context);
- const actions = find(infos, info => info.description === Diagnostics.Extract_constant.message).actions;
- const data: string[] = [];
- data.push(`// ==ORIGINAL==`);
- data.push(sourceFile.text);
- for (const action of actions) {
- const { renameLocation, edits } = refactor.extractSymbol.getEditsForAction(context, action.name);
- assert.lengthOf(edits, 1);
- data.push(`// ==SCOPE::${action.description}==`);
- const newText = textChanges.applyChanges(sourceFile.text, edits[0].textChanges);
- const newTextWithRename = newText.slice(0, renameLocation) + "/*RENAME*/" + newText.slice(renameLocation);
- data.push(newTextWithRename);
- }
- return data.join(newLineCharacter);
- });
- });
+ testExtractSymbol(caption, text, "extractConstant", Diagnostics.Extract_constant);
}
- // TODO (acasey): share?
function testExtractConstantFailed(caption: string, text: string) {
- it(caption, () => {
- const t = extractTest(text);
- const selectionRange = t.ranges.get("selection");
- if (!selectionRange) {
- throw new Error(`Test ${caption} does not specify selection range`);
- }
- const f = {
- path: "/a.ts",
- content: t.source
- };
- const host = projectSystem.createServerHost([f, projectSystem.libFile]);
- const projectService = projectSystem.createProjectService(host);
- projectService.openClientFile(f.path);
- const program = projectService.inferredProjects[0].getLanguageService().getProgram();
- const sourceFile = program.getSourceFile(f.path);
- const context: RefactorContext = {
- cancellationToken: { throwIfCancellationRequested() { }, isCancellationRequested() { return false; } },
- newLineCharacter,
- program,
- file: sourceFile,
- startPosition: selectionRange.start,
- endPosition: selectionRange.end,
- rulesProvider: getRuleProvider()
- };
- const rangeToExtract = refactor.extractSymbol.getRangeToExtract(sourceFile, createTextSpanFromBounds(selectionRange.start, selectionRange.end));
- assert.isUndefined(rangeToExtract.errors, rangeToExtract.errors && "Range error: " + rangeToExtract.errors[0].messageText);
- const infos = refactor.extractSymbol.getAvailableActions(context);
- assert.isUndefined(find(infos, info => info.description === Diagnostics.Extract_constant.message));
- });
+ testExtractSymbolFailed(caption, text, Diagnostics.Extract_constant);
}
}
diff --git a/src/harness/unittests/extractFunctions.ts b/src/harness/unittests/extractFunctions.ts
index be5d21a80ae..2497b2eec51 100644
--- a/src/harness/unittests/extractFunctions.ts
+++ b/src/harness/unittests/extractFunctions.ts
@@ -1,417 +1,7 @@
-///
-///
+///
namespace ts {
- interface Range {
- start: number;
- end: number;
- name: string;
- }
-
- interface Test {
- source: string;
- ranges: Map;
- }
-
- function extractTest(source: string): Test {
- const activeRanges: Range[] = [];
- let text = "";
- let lastPos = 0;
- let pos = 0;
- const ranges = createMap();
-
- while (pos < source.length) {
- if (source.charCodeAt(pos) === CharacterCodes.openBracket &&
- (source.charCodeAt(pos + 1) === CharacterCodes.hash || source.charCodeAt(pos + 1) === CharacterCodes.$)) {
- const saved = pos;
- pos += 2;
- const s = pos;
- consumeIdentifier();
- const e = pos;
- if (source.charCodeAt(pos) === CharacterCodes.bar) {
- pos++;
- text += source.substring(lastPos, saved);
- const name = s === e
- ? source.charCodeAt(saved + 1) === CharacterCodes.hash ? "selection" : "extracted"
- : source.substring(s, e);
- activeRanges.push({ name, start: text.length, end: undefined });
- lastPos = pos;
- continue;
- }
- else {
- pos = saved;
- }
- }
- else if (source.charCodeAt(pos) === CharacterCodes.bar && source.charCodeAt(pos + 1) === CharacterCodes.closeBracket) {
- text += source.substring(lastPos, pos);
- activeRanges[activeRanges.length - 1].end = text.length;
- const range = activeRanges.pop();
- if (range.name in ranges) {
- throw new Error(`Duplicate name of range ${range.name}`);
- }
- ranges.set(range.name, range);
- pos += 2;
- lastPos = pos;
- continue;
- }
- pos++;
- }
- text += source.substring(lastPos, pos);
-
- function consumeIdentifier() {
- while (isIdentifierPart(source.charCodeAt(pos), ScriptTarget.Latest)) {
- pos++;
- }
- }
- return { source: text, ranges };
- }
-
- const newLineCharacter = "\n";
- function getRuleProvider(action?: (opts: FormatCodeSettings) => void) {
- const options = {
- indentSize: 4,
- tabSize: 4,
- newLineCharacter,
- convertTabsToSpaces: true,
- indentStyle: ts.IndentStyle.Smart,
- insertSpaceAfterConstructor: false,
- insertSpaceAfterCommaDelimiter: true,
- insertSpaceAfterSemicolonInForStatements: true,
- insertSpaceBeforeAndAfterBinaryOperators: true,
- insertSpaceAfterKeywordsInControlFlowStatements: true,
- insertSpaceAfterFunctionKeywordForAnonymousFunctions: false,
- insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
- insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false,
- insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
- insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false,
- insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false,
- insertSpaceBeforeFunctionParenthesis: false,
- placeOpenBraceOnNewLineForFunctions: false,
- placeOpenBraceOnNewLineForControlBlocks: false,
- };
- if (action) {
- action(options);
- }
- const rulesProvider = new formatting.RulesProvider();
- rulesProvider.ensureUpToDate(options);
- return rulesProvider;
- }
-
- function testExtractRangeFailed(caption: string, s: string, expectedErrors: string[]) {
- return it(caption, () => {
- const t = extractTest(s);
- const file = createSourceFile("a.ts", t.source, ScriptTarget.Latest, /*setParentNodes*/ true);
- const selectionRange = t.ranges.get("selection");
- if (!selectionRange) {
- throw new Error(`Test ${s} does not specify selection range`);
- }
- const result = refactor.extractSymbol.getRangeToExtract(file, createTextSpanFromBounds(selectionRange.start, selectionRange.end));
- assert(result.targetRange === undefined, "failure expected");
- const sortedErrors = result.errors.map(e => e.messageText).sort();
- assert.deepEqual(sortedErrors, expectedErrors.sort(), "unexpected errors");
- });
- }
-
- function testExtractRange(s: string): void {
- const t = extractTest(s);
- const f = createSourceFile("a.ts", t.source, ScriptTarget.Latest, /*setParentNodes*/ true);
- const selectionRange = t.ranges.get("selection");
- if (!selectionRange) {
- throw new Error(`Test ${s} does not specify selection range`);
- }
- const result = refactor.extractSymbol.getRangeToExtract(f, createTextSpanFromBounds(selectionRange.start, selectionRange.end));
- const expectedRange = t.ranges.get("extracted");
- if (expectedRange) {
- let start: number, end: number;
- if (ts.isArray(result.targetRange.range)) {
- start = result.targetRange.range[0].getStart(f);
- end = ts.lastOrUndefined(result.targetRange.range).getEnd();
- }
- else {
- start = result.targetRange.range.getStart(f);
- end = result.targetRange.range.getEnd();
- }
- assert.equal(start, expectedRange.start, "incorrect start of range");
- assert.equal(end, expectedRange.end, "incorrect end of range");
- }
- else {
- assert.isTrue(!result.targetRange, `expected range to extract to be undefined`);
- }
- }
-
describe("extractMethods", () => {
- it("get extract range from selection", () => {
- testExtractRange(`
- [#|
- [$|var x = 1;
- var y = 2;|]|]
- `);
- testExtractRange(`
- [#|
- var x = 1;
- var y = 2|];
- `);
- testExtractRange(`
- [#|var x = 1|];
- var y = 2;
- `);
- testExtractRange(`
- if ([#|[#extracted|a && b && c && d|]|]) {
- }
- `);
- testExtractRange(`
- if [#|(a && b && c && d|]) {
- }
- `);
- testExtractRange(`
- if (a && b && c && d) {
- [#| [$|var x = 1;
- console.log(x);|] |]
- }
- `);
- testExtractRange(`
- [#|
- if (a) {
- return 100;
- } |]
- `);
- testExtractRange(`
- function foo() {
- [#| [$|if (a) {
- }
- return 100|] |]
- }
- `);
- testExtractRange(`
- [#|
- [$|l1:
- if (x) {
- break l1;
- }|]|]
- `);
- testExtractRange(`
- [#|
- [$|l2:
- {
- if (x) {
- }
- break l2;
- }|]|]
- `);
- testExtractRange(`
- while (true) {
- [#| if(x) {
- }
- break; |]
- }
- `);
- testExtractRange(`
- while (true) {
- [#| if(x) {
- }
- continue; |]
- }
- `);
- testExtractRange(`
- l3:
- {
- [#|
- if (x) {
- }
- break l3; |]
- }
- `);
- testExtractRange(`
- function f() {
- while (true) {
- [#|
- if (x) {
- return;
- } |]
- }
- }
- `);
- testExtractRange(`
- function f() {
- while (true) {
- [#|
- [$|if (x) {
- }
- return;|]
- |]
- }
- }
- `);
- testExtractRange(`
- function f() {
- return [#| [$|1 + 2|] |]+ 3;
- }
- }
- `);
- testExtractRange(`
- function f() {
- return [$|1 + [#|2 + 3|]|];
- }
- }
- `);
- testExtractRange(`
- function f() {
- return [$|1 + 2 + [#|3 + 4|]|];
- }
- }
- `);
- });
-
- testExtractRangeFailed("extractRangeFailed1",
- `
-namespace A {
- function f() {
- [#|
- let x = 1
- if (x) {
- return 10;
- }
- |]
- }
-}
- `,
- [
- "Cannot extract range containing conditional return statement."
- ]);
-
- testExtractRangeFailed("extractRangeFailed2",
- `
-namespace A {
- function f() {
- while (true) {
- [#|
- let x = 1
- if (x) {
- break;
- }
- |]
- }
- }
-}
- `,
- [
- "Cannot extract range containing conditional break or continue statements."
- ]);
-
- testExtractRangeFailed("extractRangeFailed3",
- `
-namespace A {
- function f() {
- while (true) {
- [#|
- let x = 1
- if (x) {
- continue;
- }
- |]
- }
- }
-}
- `,
- [
- "Cannot extract range containing conditional break or continue statements."
- ]);
-
- testExtractRangeFailed("extractRangeFailed4",
- `
-namespace A {
- function f() {
- l1: {
- [#|
- let x = 1
- if (x) {
- break l1;
- }
- |]
- }
- }
-}
- `,
- [
- "Cannot extract range containing labeled break or continue with target outside of the range."
- ]);
-
- testExtractRangeFailed("extractRangeFailed5",
- `
-namespace A {
- function f() {
- [#|
- try {
- f2()
- return 10;
- }
- catch (e) {
- }
- |]
- }
- function f2() {
- }
-}
- `,
- [
- "Cannot extract range containing conditional return statement."
- ]);
-
- testExtractRangeFailed("extractRangeFailed6",
- `
-namespace A {
- function f() {
- [#|
- try {
- f2()
- }
- catch (e) {
- return 10;
- }
- |]
- }
- function f2() {
- }
-}
- `,
- [
- "Cannot extract range containing conditional return statement."
- ]);
-
- testExtractRangeFailed("extractRangeFailed7",
- `
-function test(x: number) {
- while (x) {
- x--;
- [#|break;|]
- }
-}
- `,
- [
- "Cannot extract range containing conditional break or continue statements."
- ]);
-
- testExtractRangeFailed("extractRangeFailed8",
- `
-function test(x: number) {
- switch (x) {
- case 1:
- [#|break;|]
- }
-}
- `,
- [
- "Cannot extract range containing conditional break or continue statements."
- ]);
-
- testExtractRangeFailed("extractRangeFailed9",
- `var x = ([#||]1 + 2);`,
- [
- "Cannot extract empty range."
- ]);
-
- testExtractRangeFailed("extract-method-not-for-token-expression-statement", `[#|a|]`, ["Select more than a single identifier."]);
-
testExtractMethod("extractMethod1",
`namespace A {
let x = 1;
@@ -776,50 +366,7 @@ function parsePrimaryExpression(): any {
}`);
});
-
function testExtractMethod(caption: string, text: string) {
- it(caption, () => {
- Harness.Baseline.runBaseline(`extractMethod/${caption}.ts`, () => {
- const t = extractTest(text);
- const selectionRange = t.ranges.get("selection");
- if (!selectionRange) {
- throw new Error(`Test ${caption} does not specify selection range`);
- }
- const f = {
- path: "/a.ts",
- content: t.source
- };
- const host = projectSystem.createServerHost([f, projectSystem.libFile]);
- const projectService = projectSystem.createProjectService(host);
- projectService.openClientFile(f.path);
- const program = projectService.inferredProjects[0].getLanguageService().getProgram();
- const sourceFile = program.getSourceFile(f.path);
- const context: RefactorContext = {
- cancellationToken: { throwIfCancellationRequested() { }, isCancellationRequested() { return false; } },
- newLineCharacter,
- program,
- file: sourceFile,
- startPosition: selectionRange.start,
- endPosition: selectionRange.end,
- rulesProvider: getRuleProvider()
- };
- const rangeToExtract = refactor.extractSymbol.getRangeToExtract(sourceFile, createTextSpanFromBounds(selectionRange.start, selectionRange.end));
- assert.equal(rangeToExtract.errors, undefined, "expect no errors");
- const infos = refactor.extractSymbol.getAvailableActions(context);
- const actions = find(infos, info => info.description === Diagnostics.Extract_function.message).actions;
- const data: string[] = [];
- data.push(`// ==ORIGINAL==`);
- data.push(sourceFile.text);
- for (const action of actions) {
- const { renameLocation, edits } = refactor.extractSymbol.getEditsForAction(context, action.name);
- assert.lengthOf(edits, 1);
- data.push(`// ==SCOPE::${action.description}==`);
- const newText = textChanges.applyChanges(sourceFile.text, edits[0].textChanges);
- const newTextWithRename = newText.slice(0, renameLocation) + "/*RENAME*/" + newText.slice(renameLocation);
- data.push(newTextWithRename);
- }
- return data.join(newLineCharacter);
- });
- });
+ testExtractSymbol(caption, text, "extractMethod", Diagnostics.Extract_function);
}
}
diff --git a/src/harness/unittests/extractRanges.ts b/src/harness/unittests/extractRanges.ts
new file mode 100644
index 00000000000..55535a6dc44
--- /dev/null
+++ b/src/harness/unittests/extractRanges.ts
@@ -0,0 +1,319 @@
+///
+
+namespace ts {
+ function testExtractRangeFailed(caption: string, s: string, expectedErrors: string[]) {
+ return it(caption, () => {
+ const t = extractTest(s);
+ const file = createSourceFile("a.ts", t.source, ScriptTarget.Latest, /*setParentNodes*/ true);
+ const selectionRange = t.ranges.get("selection");
+ if (!selectionRange) {
+ throw new Error(`Test ${s} does not specify selection range`);
+ }
+ const result = refactor.extractSymbol.getRangeToExtract(file, createTextSpanFromBounds(selectionRange.start, selectionRange.end));
+ assert(result.targetRange === undefined, "failure expected");
+ const sortedErrors = result.errors.map(e => e.messageText).sort();
+ assert.deepEqual(sortedErrors, expectedErrors.sort(), "unexpected errors");
+ });
+ }
+
+ function testExtractRange(s: string): void {
+ const t = extractTest(s);
+ const f = createSourceFile("a.ts", t.source, ScriptTarget.Latest, /*setParentNodes*/ true);
+ const selectionRange = t.ranges.get("selection");
+ if (!selectionRange) {
+ throw new Error(`Test ${s} does not specify selection range`);
+ }
+ const result = refactor.extractSymbol.getRangeToExtract(f, createTextSpanFromBounds(selectionRange.start, selectionRange.end));
+ const expectedRange = t.ranges.get("extracted");
+ if (expectedRange) {
+ let start: number, end: number;
+ if (ts.isArray(result.targetRange.range)) {
+ start = result.targetRange.range[0].getStart(f);
+ end = ts.lastOrUndefined(result.targetRange.range).getEnd();
+ }
+ else {
+ start = result.targetRange.range.getStart(f);
+ end = result.targetRange.range.getEnd();
+ }
+ assert.equal(start, expectedRange.start, "incorrect start of range");
+ assert.equal(end, expectedRange.end, "incorrect end of range");
+ }
+ else {
+ assert.isTrue(!result.targetRange, `expected range to extract to be undefined`);
+ }
+ }
+
+ describe("extractRanges", () => {
+ it("get extract range from selection", () => {
+ testExtractRange(`
+ [#|
+ [$|var x = 1;
+ var y = 2;|]|]
+ `);
+ testExtractRange(`
+ [#|
+ var x = 1;
+ var y = 2|];
+ `);
+ testExtractRange(`
+ [#|var x = 1|];
+ var y = 2;
+ `);
+ testExtractRange(`
+ if ([#|[#extracted|a && b && c && d|]|]) {
+ }
+ `);
+ testExtractRange(`
+ if [#|(a && b && c && d|]) {
+ }
+ `);
+ testExtractRange(`
+ if (a && b && c && d) {
+ [#| [$|var x = 1;
+ console.log(x);|] |]
+ }
+ `);
+ testExtractRange(`
+ [#|
+ if (a) {
+ return 100;
+ } |]
+ `);
+ testExtractRange(`
+ function foo() {
+ [#| [$|if (a) {
+ }
+ return 100|] |]
+ }
+ `);
+ testExtractRange(`
+ [#|
+ [$|l1:
+ if (x) {
+ break l1;
+ }|]|]
+ `);
+ testExtractRange(`
+ [#|
+ [$|l2:
+ {
+ if (x) {
+ }
+ break l2;
+ }|]|]
+ `);
+ testExtractRange(`
+ while (true) {
+ [#| if(x) {
+ }
+ break; |]
+ }
+ `);
+ testExtractRange(`
+ while (true) {
+ [#| if(x) {
+ }
+ continue; |]
+ }
+ `);
+ testExtractRange(`
+ l3:
+ {
+ [#|
+ if (x) {
+ }
+ break l3; |]
+ }
+ `);
+ testExtractRange(`
+ function f() {
+ while (true) {
+ [#|
+ if (x) {
+ return;
+ } |]
+ }
+ }
+ `);
+ testExtractRange(`
+ function f() {
+ while (true) {
+ [#|
+ [$|if (x) {
+ }
+ return;|]
+ |]
+ }
+ }
+ `);
+ testExtractRange(`
+ function f() {
+ return [#| [$|1 + 2|] |]+ 3;
+ }
+ }
+ `);
+ testExtractRange(`
+ function f() {
+ return [$|1 + [#|2 + 3|]|];
+ }
+ }
+ `);
+ testExtractRange(`
+ function f() {
+ return [$|1 + 2 + [#|3 + 4|]|];
+ }
+ }
+ `);
+ });
+
+ testExtractRangeFailed("extractRangeFailed1",
+ `
+namespace A {
+function f() {
+ [#|
+ let x = 1
+ if (x) {
+ return 10;
+ }
+ |]
+}
+}
+ `,
+ [
+ "Cannot extract range containing conditional return statement."
+ ]);
+
+ testExtractRangeFailed("extractRangeFailed2",
+ `
+namespace A {
+function f() {
+ while (true) {
+ [#|
+ let x = 1
+ if (x) {
+ break;
+ }
+ |]
+ }
+}
+}
+ `,
+ [
+ "Cannot extract range containing conditional break or continue statements."
+ ]);
+
+ testExtractRangeFailed("extractRangeFailed3",
+ `
+namespace A {
+function f() {
+ while (true) {
+ [#|
+ let x = 1
+ if (x) {
+ continue;
+ }
+ |]
+ }
+}
+}
+ `,
+ [
+ "Cannot extract range containing conditional break or continue statements."
+ ]);
+
+ testExtractRangeFailed("extractRangeFailed4",
+ `
+namespace A {
+function f() {
+ l1: {
+ [#|
+ let x = 1
+ if (x) {
+ break l1;
+ }
+ |]
+ }
+}
+}
+ `,
+ [
+ "Cannot extract range containing labeled break or continue with target outside of the range."
+ ]);
+
+ testExtractRangeFailed("extractRangeFailed5",
+ `
+namespace A {
+function f() {
+ [#|
+ try {
+ f2()
+ return 10;
+ }
+ catch (e) {
+ }
+ |]
+}
+function f2() {
+}
+}
+ `,
+ [
+ "Cannot extract range containing conditional return statement."
+ ]);
+
+ testExtractRangeFailed("extractRangeFailed6",
+ `
+namespace A {
+function f() {
+ [#|
+ try {
+ f2()
+ }
+ catch (e) {
+ return 10;
+ }
+ |]
+}
+function f2() {
+}
+}
+ `,
+ [
+ "Cannot extract range containing conditional return statement."
+ ]);
+
+ testExtractRangeFailed("extractRangeFailed7",
+ `
+function test(x: number) {
+while (x) {
+ x--;
+ [#|break;|]
+}
+}
+ `,
+ [
+ "Cannot extract range containing conditional break or continue statements."
+ ]);
+
+ testExtractRangeFailed("extractRangeFailed8",
+ `
+function test(x: number) {
+switch (x) {
+ case 1:
+ [#|break;|]
+}
+}
+ `,
+ [
+ "Cannot extract range containing conditional break or continue statements."
+ ]);
+
+ testExtractRangeFailed("extractRangeFailed9",
+ `var x = ([#||]1 + 2);`,
+ [
+ "Cannot extract empty range."
+ ]);
+
+ testExtractRangeFailed("extract-method-not-for-token-expression-statement", `[#|a|]`, ["Select more than a single identifier."]);
+ });
+}
\ No newline at end of file
diff --git a/src/harness/unittests/extractTestHelpers.ts b/src/harness/unittests/extractTestHelpers.ts
new file mode 100644
index 00000000000..e619f9343d8
--- /dev/null
+++ b/src/harness/unittests/extractTestHelpers.ts
@@ -0,0 +1,177 @@
+///
+///
+
+namespace ts {
+ export interface Range {
+ start: number;
+ end: number;
+ name: string;
+ }
+
+ export interface Test {
+ source: string;
+ ranges: Map;
+ }
+
+ export function extractTest(source: string): Test {
+ const activeRanges: Range[] = [];
+ let text = "";
+ let lastPos = 0;
+ let pos = 0;
+ const ranges = createMap();
+
+ while (pos < source.length) {
+ if (source.charCodeAt(pos) === CharacterCodes.openBracket &&
+ (source.charCodeAt(pos + 1) === CharacterCodes.hash || source.charCodeAt(pos + 1) === CharacterCodes.$)) {
+ const saved = pos;
+ pos += 2;
+ const s = pos;
+ consumeIdentifier();
+ const e = pos;
+ if (source.charCodeAt(pos) === CharacterCodes.bar) {
+ pos++;
+ text += source.substring(lastPos, saved);
+ const name = s === e
+ ? source.charCodeAt(saved + 1) === CharacterCodes.hash ? "selection" : "extracted"
+ : source.substring(s, e);
+ activeRanges.push({ name, start: text.length, end: undefined });
+ lastPos = pos;
+ continue;
+ }
+ else {
+ pos = saved;
+ }
+ }
+ else if (source.charCodeAt(pos) === CharacterCodes.bar && source.charCodeAt(pos + 1) === CharacterCodes.closeBracket) {
+ text += source.substring(lastPos, pos);
+ activeRanges[activeRanges.length - 1].end = text.length;
+ const range = activeRanges.pop();
+ if (range.name in ranges) {
+ throw new Error(`Duplicate name of range ${range.name}`);
+ }
+ ranges.set(range.name, range);
+ pos += 2;
+ lastPos = pos;
+ continue;
+ }
+ pos++;
+ }
+ text += source.substring(lastPos, pos);
+
+ function consumeIdentifier() {
+ while (isIdentifierPart(source.charCodeAt(pos), ScriptTarget.Latest)) {
+ pos++;
+ }
+ }
+ return { source: text, ranges };
+ }
+
+ export const newLineCharacter = "\n";
+ export function getRuleProvider(action?: (opts: FormatCodeSettings) => void) {
+ const options = {
+ indentSize: 4,
+ tabSize: 4,
+ newLineCharacter,
+ convertTabsToSpaces: true,
+ indentStyle: ts.IndentStyle.Smart,
+ insertSpaceAfterConstructor: false,
+ insertSpaceAfterCommaDelimiter: true,
+ insertSpaceAfterSemicolonInForStatements: true,
+ insertSpaceBeforeAndAfterBinaryOperators: true,
+ insertSpaceAfterKeywordsInControlFlowStatements: true,
+ insertSpaceAfterFunctionKeywordForAnonymousFunctions: false,
+ insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
+ insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false,
+ insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
+ insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false,
+ insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false,
+ insertSpaceBeforeFunctionParenthesis: false,
+ placeOpenBraceOnNewLineForFunctions: false,
+ placeOpenBraceOnNewLineForControlBlocks: false,
+ };
+ if (action) {
+ action(options);
+ }
+ const rulesProvider = new formatting.RulesProvider();
+ rulesProvider.ensureUpToDate(options);
+ return rulesProvider;
+ }
+
+ export function testExtractSymbol(caption: string, text: string, baselineFolder: string, description: DiagnosticMessage) {
+ it(caption, () => {
+ Harness.Baseline.runBaseline(`${baselineFolder}/${caption}.ts`, () => {
+ const t = extractTest(text);
+ const selectionRange = t.ranges.get("selection");
+ if (!selectionRange) {
+ throw new Error(`Test ${caption} does not specify selection range`);
+ }
+ const f = {
+ path: "/a.ts",
+ content: t.source
+ };
+ const host = projectSystem.createServerHost([f, projectSystem.libFile]);
+ const projectService = projectSystem.createProjectService(host);
+ projectService.openClientFile(f.path);
+ const program = projectService.inferredProjects[0].getLanguageService().getProgram();
+ const sourceFile = program.getSourceFile(f.path);
+ const context: RefactorContext = {
+ cancellationToken: { throwIfCancellationRequested() { }, isCancellationRequested() { return false; } },
+ newLineCharacter,
+ program,
+ file: sourceFile,
+ startPosition: selectionRange.start,
+ endPosition: selectionRange.end,
+ rulesProvider: getRuleProvider()
+ };
+ const rangeToExtract = refactor.extractSymbol.getRangeToExtract(sourceFile, createTextSpanFromBounds(selectionRange.start, selectionRange.end));
+ assert.equal(rangeToExtract.errors, undefined, rangeToExtract.errors && "Range error: " + rangeToExtract.errors[0].messageText);
+ const infos = refactor.extractSymbol.getAvailableActions(context);
+ const actions = find(infos, info => info.description === description.message).actions;
+ const data: string[] = [];
+ data.push(`// ==ORIGINAL==`);
+ data.push(sourceFile.text);
+ for (const action of actions) {
+ const { renameLocation, edits } = refactor.extractSymbol.getEditsForAction(context, action.name);
+ assert.lengthOf(edits, 1);
+ data.push(`// ==SCOPE::${action.description}==`);
+ const newText = textChanges.applyChanges(sourceFile.text, edits[0].textChanges);
+ const newTextWithRename = newText.slice(0, renameLocation) + "/*RENAME*/" + newText.slice(renameLocation);
+ data.push(newTextWithRename);
+ }
+ return data.join(newLineCharacter);
+ });
+ });
+ }
+
+ export function testExtractSymbolFailed(caption: string, text: string, description: DiagnosticMessage) {
+ it(caption, () => {
+ const t = extractTest(text);
+ const selectionRange = t.ranges.get("selection");
+ if (!selectionRange) {
+ throw new Error(`Test ${caption} does not specify selection range`);
+ }
+ const f = {
+ path: "/a.ts",
+ content: t.source
+ };
+ const host = projectSystem.createServerHost([f, projectSystem.libFile]);
+ const projectService = projectSystem.createProjectService(host);
+ projectService.openClientFile(f.path);
+ const program = projectService.inferredProjects[0].getLanguageService().getProgram();
+ const sourceFile = program.getSourceFile(f.path);
+ const context: RefactorContext = {
+ cancellationToken: { throwIfCancellationRequested() { }, isCancellationRequested() { return false; } },
+ newLineCharacter,
+ program,
+ file: sourceFile,
+ startPosition: selectionRange.start,
+ endPosition: selectionRange.end,
+ rulesProvider: getRuleProvider()
+ };
+ const rangeToExtract = refactor.extractSymbol.getRangeToExtract(sourceFile, createTextSpanFromBounds(selectionRange.start, selectionRange.end));
+ assert.isUndefined(rangeToExtract.errors, rangeToExtract.errors && "Range error: " + rangeToExtract.errors[0].messageText);
+ const infos = refactor.extractSymbol.getAvailableActions(context);
+ assert.isUndefined(find(infos, info => info.description === description.message));
+ });
+ }
+}
\ No newline at end of file