mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-23 10:29:01 -06:00
Split range tests and helpers out of extractFunctions.ts
This commit is contained in:
parent
52ab05e99d
commit
697bce74b8
@ -140,6 +140,8 @@ var harnessSources = harnessCoreSources.concat([
|
||||
"initializeTSConfig.ts",
|
||||
"extractConstants.ts",
|
||||
"extractFunctions.ts",
|
||||
"extractRanges.ts",
|
||||
"extractTestHelpers.ts",
|
||||
"printer.ts",
|
||||
"textChanges.ts",
|
||||
"telemetry.ts",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -1,104 +1,6 @@
|
||||
/// <reference path="..\harness.ts" />
|
||||
/// <reference path="tsserverProjectSystem.ts" />
|
||||
/// <reference path="extractTestHelpers.ts" />
|
||||
|
||||
namespace ts {
|
||||
interface Range {
|
||||
start: number;
|
||||
end: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface Test {
|
||||
source: string;
|
||||
ranges: Map<Range>;
|
||||
}
|
||||
|
||||
// TODO (acasey): share
|
||||
function extractTest(source: string): Test {
|
||||
const activeRanges: Range[] = [];
|
||||
let text = "";
|
||||
let lastPos = 0;
|
||||
let pos = 0;
|
||||
const ranges = createMap<Range>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,417 +1,7 @@
|
||||
/// <reference path="..\harness.ts" />
|
||||
/// <reference path="tsserverProjectSystem.ts" />
|
||||
/// <reference path="extractTestHelpers.ts" />
|
||||
|
||||
namespace ts {
|
||||
interface Range {
|
||||
start: number;
|
||||
end: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface Test {
|
||||
source: string;
|
||||
ranges: Map<Range>;
|
||||
}
|
||||
|
||||
function extractTest(source: string): Test {
|
||||
const activeRanges: Range[] = [];
|
||||
let text = "";
|
||||
let lastPos = 0;
|
||||
let pos = 0;
|
||||
const ranges = createMap<Range>();
|
||||
|
||||
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 => <string>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);
|
||||
}
|
||||
}
|
||||
|
||||
319
src/harness/unittests/extractRanges.ts
Normal file
319
src/harness/unittests/extractRanges.ts
Normal file
@ -0,0 +1,319 @@
|
||||
/// <reference path="extractTestHelpers.ts" />
|
||||
|
||||
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 => <string>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."]);
|
||||
});
|
||||
}
|
||||
177
src/harness/unittests/extractTestHelpers.ts
Normal file
177
src/harness/unittests/extractTestHelpers.ts
Normal file
@ -0,0 +1,177 @@
|
||||
/// <reference path="..\harness.ts" />
|
||||
/// <reference path="tsserverProjectSystem.ts" />
|
||||
|
||||
namespace ts {
|
||||
export interface Range {
|
||||
start: number;
|
||||
end: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface Test {
|
||||
source: string;
|
||||
ranges: Map<Range>;
|
||||
}
|
||||
|
||||
export function extractTest(source: string): Test {
|
||||
const activeRanges: Range[] = [];
|
||||
let text = "";
|
||||
let lastPos = 0;
|
||||
let pos = 0;
|
||||
const ranges = createMap<Range>();
|
||||
|
||||
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));
|
||||
});
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user