Extract Method (squash)

This commit is contained in:
Ryan Cavanaugh 2017-08-04 16:10:33 -07:00
parent 8f7a582fc5
commit c7f665faa1
63 changed files with 4367 additions and 77 deletions

7
.vscode/tasks.json vendored
View file

@ -18,6 +18,13 @@
"problemMatcher": [
"$tsc"
]
},
{
"taskName": "tests",
"showOutput": "silent",
"problemMatcher": [
"$tsc"
]
}
]
}

View file

@ -133,6 +133,7 @@ var harnessSources = harnessCoreSources.concat([
"projectErrors.ts",
"matchFiles.ts",
"initializeTSConfig.ts",
"extractMethods.ts",
"printer.ts",
"textChanges.ts",
"telemetry.ts",

View file

@ -223,11 +223,10 @@ namespace ts {
getSuggestionForNonexistentProperty: (node, type) => unescapeLeadingUnderscores(getSuggestionForNonexistentProperty(node, type)),
getSuggestionForNonexistentSymbol: (location, name, meaning) => unescapeLeadingUnderscores(getSuggestionForNonexistentSymbol(location, escapeLeadingUnderscores(name), meaning)),
getBaseConstraintOfType,
getJsxNamespace: () => unescapeLeadingUnderscores(getJsxNamespace()),
resolveNameAtLocation(location: Node, name: string, meaning: SymbolFlags): Symbol | undefined {
location = getParseTreeNode(location);
return resolveName(location, escapeLeadingUnderscores(name), meaning, /*nameNotFoundMessage*/ undefined, escapeLeadingUnderscores(name));
resolveName(name, location, meaning) {
return resolveName(location, name as __String, meaning, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined);
},
getJsxNamespace: () => unescapeLeadingUnderscores(getJsxNamespace()),
};
const tupleTypes: GenericType[] = [];

View file

@ -3673,5 +3673,15 @@
"Convert function '{0}' to class": {
"category": "Message",
"code": 95002
},
"Extract function": {
"category": "Message",
"code": 95003
},
"Extract function into '{0}'": {
"category": "Message",
"code": 95004
}
}

View file

@ -394,7 +394,7 @@ namespace ts {
function shouldVisitNode(node: Node): boolean {
return (node.transformFlags & TransformFlags.ContainsES2015) !== 0
|| convertedLoopState !== undefined
|| (hierarchyFacts & HierarchyFacts.ConstructorWithCapturedSuper && isStatement(node))
|| (hierarchyFacts & HierarchyFacts.ConstructorWithCapturedSuper && isStatementOrBlock(node))
|| (isIterationStatement(node, /*lookInLabeledStatements*/ false) && shouldConvertIterationStatementBody(node))
|| isTypeScriptClassWrapper(node);
}

View file

@ -2606,9 +2606,8 @@ namespace ts {
* Does not include properties of primitive types.
*/
/* @internal */ getAllPossiblePropertiesOfType(type: Type): Symbol[];
/* @internal */ resolveName(name: string, location: Node, meaning: SymbolFlags): Symbol | undefined;
/* @internal */ getJsxNamespace(): string;
/* @internal */ resolveNameAtLocation(location: Node, name: string, meaning: SymbolFlags): Symbol | undefined;
}
export enum NodeBuilderFlags {

View file

@ -693,7 +693,7 @@ namespace ts {
// At this point, node is either a qualified name or an identifier
Debug.assert(node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.QualifiedName || node.kind === SyntaxKind.PropertyAccessExpression,
"'node' was expected to be a qualified name, identifier or property access in 'isPartOfTypeNode'.");
// falls through
// falls through
case SyntaxKind.QualifiedName:
case SyntaxKind.PropertyAccessExpression:
case SyntaxKind.ThisKeyword:
@ -968,7 +968,7 @@ namespace ts {
if (!includeArrowFunctions) {
continue;
}
// falls through
// falls through
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.FunctionExpression:
case SyntaxKind.ModuleDeclaration:
@ -1027,7 +1027,7 @@ namespace ts {
if (!stopOnFunctions) {
continue;
}
// falls through
// falls through
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertySignature:
case SyntaxKind.MethodDeclaration:
@ -1211,7 +1211,7 @@ namespace ts {
if (node.parent.kind === SyntaxKind.TypeQuery || isJSXTagName(node)) {
return true;
}
// falls through
// falls through
case SyntaxKind.NumericLiteral:
case SyntaxKind.StringLiteral:
case SyntaxKind.ThisKeyword:
@ -1499,8 +1499,8 @@ namespace ts {
parent.parent.kind === SyntaxKind.VariableStatement;
const variableStatementNode =
isInitializerOfVariableDeclarationInStatement ? parent.parent.parent :
isVariableOfVariableDeclarationStatement ? parent.parent :
undefined;
isVariableOfVariableDeclarationStatement ? parent.parent :
undefined;
if (variableStatementNode) {
getJSDocCommentsAndTagsWorker(variableStatementNode);
}
@ -1614,7 +1614,7 @@ namespace ts {
if (isInJavaScriptFile(node)) {
if (node.type && node.type.kind === SyntaxKind.JSDocVariadicType ||
forEach(getJSDocParameterTags(node),
t => t.typeExpression && t.typeExpression.type.kind === SyntaxKind.JSDocVariadicType)) {
t => t.typeExpression && t.typeExpression.type.kind === SyntaxKind.JSDocVariadicType)) {
return true;
}
}
@ -1907,7 +1907,7 @@ namespace ts {
if (node.asteriskToken) {
flags |= FunctionFlags.Generator;
}
// falls through
// falls through
case SyntaxKind.ArrowFunction:
if (hasModifier(node, ModifierFlags.Async)) {
flags |= FunctionFlags.Async;
@ -5123,6 +5123,19 @@ namespace ts {
return isUnaryExpressionKind(skipPartiallyEmittedExpressions(node).kind);
}
/* @internal */
export function isUnaryExpressionWithWrite(expr: Node): expr is PrefixUnaryExpression | PostfixUnaryExpression {
switch (expr.kind) {
case SyntaxKind.PostfixUnaryExpression:
return true;
case SyntaxKind.PrefixUnaryExpression:
return (<PrefixUnaryExpression>expr).operator === SyntaxKind.PlusPlusToken ||
(<PrefixUnaryExpression>expr).operator === SyntaxKind.MinusMinusToken;
default:
return false;
}
}
function isExpressionKind(kind: SyntaxKind) {
return kind === SyntaxKind.ConditionalExpression
|| kind === SyntaxKind.YieldExpression
@ -5337,7 +5350,25 @@ namespace ts {
const kind = node.kind;
return isStatementKindButNotDeclarationKind(kind)
|| isDeclarationStatementKind(kind)
|| kind === SyntaxKind.Block;
|| isBlockStatement(node);
}
/* @internal */
export function isStatementOrBlock(node: Node): node is Statement {
const kind = node.kind;
return isStatementKindButNotDeclarationKind(kind)
|| isDeclarationStatementKind(kind)
|| node.kind === SyntaxKind.Block;
}
function isBlockStatement(node: Node): node is Block {
if (node.kind !== SyntaxKind.Block) return false;
if (node.parent !== undefined) {
if (node.parent.kind === SyntaxKind.TryStatement || node.parent.kind === SyntaxKind.CatchClause) {
return false;
}
}
return !isFunctionBlock(node);
}
// Module references

View file

@ -187,6 +187,9 @@ namespace FourSlash {
// The current caret position in the active file
public currentCaretPosition = 0;
// The position of the end of the current selection, or -1 if nothing is selected
public selectionEnd = -1;
public lastKnownMarker = "";
// The file that's currently 'opened'
@ -433,11 +436,19 @@ namespace FourSlash {
public goToPosition(pos: number) {
this.currentCaretPosition = pos;
this.selectionEnd = -1;
}
public select(startMarker: string, endMarker: string) {
const start = this.getMarkerByName(startMarker), end = this.getMarkerByName(endMarker);
this.goToPosition(start.position);
this.selectionEnd = end.position;
}
public moveCaretRight(count = 1) {
this.currentCaretPosition += count;
this.currentCaretPosition = Math.min(this.currentCaretPosition, this.getFileContent(this.activeFile.fileName).length);
this.selectionEnd = -1;
}
// Opens a file given its 0-based index or fileName
@ -980,9 +991,9 @@ namespace FourSlash {
}
for (const reference of expectedReferences) {
const {fileName, start, end} = reference;
const { fileName, start, end } = reference;
if (reference.marker && reference.marker.data) {
const {isWriteAccess, isDefinition} = reference.marker.data;
const { isWriteAccess, isDefinition } = reference.marker.data;
this.verifyReferencesWorker(actualReferences, fileName, start, end, isWriteAccess, isDefinition);
}
else {
@ -1193,7 +1204,7 @@ namespace FourSlash {
displayParts: ts.SymbolDisplayPart[],
documentation: ts.SymbolDisplayPart[],
tags: ts.JSDocTagInfo[]
) {
) {
const actualQuickInfo = this.languageService.getQuickInfoAtPosition(this.activeFile.fileName, this.currentCaretPosition);
assert.equal(actualQuickInfo.kind, kind, this.messageAtLastKnownMarker("QuickInfo kind"));
@ -1789,19 +1800,16 @@ namespace FourSlash {
// We get back a set of edits, but langSvc.editScript only accepts one at a time. Use this to keep track
// of the incremental offset from each edit to the next. We assume these edit ranges don't overlap
edits = edits.sort((a, b) => a.span.start - b.span.start);
for (let i = 0; i < edits.length - 1; i++) {
const firstEditSpan = edits[i].span;
const firstEditEnd = firstEditSpan.start + firstEditSpan.length;
assert.isTrue(firstEditEnd <= edits[i + 1].span.start);
}
// Copy this so we don't ruin someone else's copy
edits = JSON.parse(JSON.stringify(edits));
// Get a snapshot of the content of the file so we can make sure any formatting edits didn't destroy non-whitespace characters
const oldContent = this.getFileContent(fileName);
let runningOffset = 0;
for (const edit of edits) {
const offsetStart = edit.span.start + runningOffset;
for (let i = 0; i < edits.length; i++) {
const edit = edits[i];
const offsetStart = edit.span.start;
const offsetEnd = offsetStart + edit.span.length;
this.editScriptAndUpdateMarkers(fileName, offsetStart, offsetEnd, edit.newText);
const editDelta = edit.newText.length - edit.span.length;
@ -1816,8 +1824,13 @@ namespace FourSlash {
}
}
runningOffset += editDelta;
// TODO: Consider doing this at least some of the time for higher fidelity. Currently causes a failure (bug 707150)
// this.languageService.getScriptLexicalStructure(fileName);
// Update positions of any future edits affected by this change
for (let j = i + 1; j < edits.length; j++) {
if (edits[j].span.start >= edits[i].span.start) {
edits[j].span.start += editDelta;
}
}
}
if (isFormattingEdit) {
@ -1901,7 +1914,7 @@ namespace FourSlash {
this.goToPosition(len);
}
public goToRangeStart({fileName, start}: Range) {
public goToRangeStart({ fileName, start }: Range) {
this.openFile(fileName);
this.goToPosition(start);
}
@ -2075,7 +2088,7 @@ namespace FourSlash {
return result;
}
private rangeText({fileName, start, end}: Range): string {
private rangeText({ fileName, start, end }: Range): string {
return this.getFileContent(fileName).slice(start, end);
}
@ -2361,7 +2374,7 @@ namespace FourSlash {
private applyCodeActions(actions: ts.CodeAction[], index?: number): void {
if (index === undefined) {
if (!(actions && actions.length === 1)) {
this.raiseError(`Should find exactly one codefix, but ${actions ? actions.length : "none"} found. ${actions ? actions.map(a => `${Harness.IO.newLine()} "${a.description}"`) : "" }`);
this.raiseError(`Should find exactly one codefix, but ${actions ? actions.length : "none"} found. ${actions ? actions.map(a => `${Harness.IO.newLine()} "${a.description}"`) : ""}`);
}
index = 0;
}
@ -2736,6 +2749,30 @@ namespace FourSlash {
}
}
private getSelection() {
return ({
pos: this.currentCaretPosition,
end: this.selectionEnd === -1 ? this.currentCaretPosition : this.selectionEnd
});
}
public verifyRefactorAvailable(negative: boolean, name?: string, subName?: string) {
const selection = this.getSelection();
let refactors = this.languageService.getApplicableRefactors(this.activeFile.fileName, selection) || [];
if (name) {
refactors = refactors.filter(r => r.name === name && (subName === undefined || r.actions.some(a => a.name === subName)));
}
const isAvailable = refactors.length > 0;
if (negative && isAvailable) {
this.raiseError(`verifyApplicableRefactorAvailableForRange failed - expected no refactor but found some.`);
}
else if (!negative && !isAvailable) {
this.raiseError(`verifyApplicableRefactorAvailableForRange failed - expected a refactor but found none.`);
}
}
public verifyApplicableRefactorAvailableForRange(negative: boolean) {
const ranges = this.getRanges();
if (!(ranges && ranges.length === 1)) {
@ -2752,6 +2789,20 @@ namespace FourSlash {
}
}
public applyRefactor(refactorName: string, actionName: string) {
const range = this.getSelection();
const refactors = this.languageService.getApplicableRefactors(this.activeFile.fileName, range);
const refactor = ts.find(refactors, r => r.name === refactorName);
if (!refactor) {
this.raiseError(`The expected refactor: ${refactorName} is not available at the marker location.`);
}
const editInfo = this.languageService.getEditsForRefactor(this.activeFile.fileName, this.formatCodeSettings, range, refactorName, actionName);
for (const edit of editInfo.edits) {
this.applyEdits(edit.fileName, edit.textChanges, /*isFormattingEdit*/ false);
}
}
public verifyFileAfterApplyingRefactorAtMarker(
markerName: string,
expectedContent: string,
@ -3496,6 +3547,10 @@ namespace FourSlashInterface {
public file(indexOrName: any, content?: string, scriptKindName?: string): void {
this.state.openFile(indexOrName, content, scriptKindName);
}
public select(startMarker: string, endMarker: string) {
this.state.select(startMarker, endMarker);
}
}
export class VerifyNegatable {
@ -3617,6 +3672,10 @@ namespace FourSlashInterface {
public applicableRefactorAvailableForRange() {
this.state.verifyApplicableRefactorAvailableForRange(this.negative);
}
public refactorAvailable(name?: string, subName?: string) {
this.state.verifyRefactorAvailable(this.negative, name, subName);
}
}
export class Verify extends VerifyNegatable {
@ -4012,6 +4071,10 @@ namespace FourSlashInterface {
public disableFormatting() {
this.state.enableFormatting = false;
}
public applyRefactor(refactorName: string, actionName: string) {
this.state.applyRefactor(refactorName, actionName);
}
}
export class Debug {

View file

@ -0,0 +1,591 @@
/// <reference path="..\harness.ts" />
/// <reference path="tsserverProjectSystem.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.extractMethod.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.extractMethod.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."
]);
testExtractMethod("extractMethod1",
`namespace A {
let x = 1;
function foo() {
}
namespace B {
function a() {
let a = 1;
[#|
let y = 5;
let z = x;
a = y;
foo();|]
}
}
}`);
testExtractMethod("extractMethod2",
`namespace A {
let x = 1;
function foo() {
}
namespace B {
function a() {
[#|
let y = 5;
let z = x;
return foo();|]
}
}
}`);
testExtractMethod("extractMethod3",
`namespace A {
function foo() {
}
namespace B {
function* a(z: number) {
[#|
let y = 5;
yield z;
return foo();|]
}
}
}`);
testExtractMethod("extractMethod4",
`namespace A {
function foo() {
}
namespace B {
async function a(z: number, z1: any) {
[#|
let y = 5;
if (z) {
await z1;
}
return foo();|]
}
}
}`);
testExtractMethod("extractMethod5",
`namespace A {
let x = 1;
export function foo() {
}
namespace B {
function a() {
let a = 1;
[#|
let y = 5;
let z = x;
a = y;
foo();|]
}
}
}`);
testExtractMethod("extractMethod6",
`namespace A {
let x = 1;
export function foo() {
}
namespace B {
function a() {
let a = 1;
[#|
let y = 5;
let z = x;
a = y;
return foo();|]
}
}
}`);
testExtractMethod("extractMethod7",
`namespace A {
let x = 1;
export namespace C {
export function foo() {
}
}
namespace B {
function a() {
let a = 1;
[#|
let y = 5;
let z = x;
a = y;
return C.foo();|]
}
}
}`);
testExtractMethod("extractMethod8",
`namespace A {
let x = 1;
namespace B {
function a() {
let a1 = 1;
return 1 + [#|a1 + x|] + 100;
}
}
}`);
testExtractMethod("extractMethod9",
`namespace A {
export interface I { x: number };
namespace B {
function a() {
[#|let a1: I = { x: 1 };
return a1.x + 10;|]
}
}
}`);
testExtractMethod("extractMethod10",
`namespace A {
export interface I { x: number };
class C {
a() {
let z = 1;
[#|let a1: I = { x: 1 };
return a1.x + 10;|]
}
}
}`);
testExtractMethod("extractMethod11",
`namespace A {
let y = 1;
class C {
a() {
let z = 1;
[#|let a1 = { x: 1 };
y = 10;
z = 42;
return a1.x + 10;|]
}
}
}`);
testExtractMethod("extractMethod12",
`namespace A {
let y = 1;
class C {
b() {}
a() {
let z = 1;
[#|let a1 = { x: 1 };
y = 10;
z = 42;
this.b();
return a1.x + 10;|]
}
}
}`);
});
function testExtractMethod(caption: string, text: string) {
it(caption, () => {
Harness.Baseline.runBaseline(`extractMethod/${caption}.js`, () => {
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]);
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: -1,
rulesProvider: getRuleProvider()
};
const result = refactor.extractMethod.getRangeToExtract(sourceFile, createTextSpanFromBounds(selectionRange.start, selectionRange.end));
assert.equal(result.errors, undefined, "expect no errors");
const results = refactor.extractMethod.extractRange(result.targetRange, context);
const data: string[] = [];
data.push(`==ORIGINAL==`);
data.push(sourceFile.text);
for (const r of results) {
const changes = refactor.extractMethod.extractRange(result.targetRange, context, results.indexOf(r))[0].changes;
data.push(`==SCOPE::${r.scopeDescription}==`);
data.push(textChanges.applyChanges(sourceFile.text, changes[0].textChanges));
}
return data.join(newLineCharacter);
});
});
}
}

View file

@ -147,7 +147,7 @@ namespace ts.codefix {
}
else if (isJsxOpeningLikeElement(token.parent) && token.parent.tagName === token) {
// The error wasn't for the symbolAtLocation, it was for the JSX tag itself, which needs access to e.g. `React`.
symbol = checker.getAliasedSymbol(checker.resolveNameAtLocation(token, checker.getJsxNamespace(), SymbolFlags.Value));
symbol = checker.getAliasedSymbol(checker.resolveName(checker.getJsxNamespace(), token.parent.tagName, SymbolFlags.Value));
symbolName = symbol.name;
}
else {

View file

@ -1,6 +1,6 @@
/* @internal */
namespace ts.refactor {
namespace ts.refactor.convertFunctionToES6Class {
const actionName = "convert";
const convertFunctionToES6Class: Refactor = {

File diff suppressed because it is too large Load diff

View file

@ -1 +1,2 @@
/// <reference path="convertFunctionToEs6Class.ts" />
/// <reference path="extractMethod.ts" />

View file

@ -64,7 +64,7 @@ namespace ts.textChanges {
*/
export type ConfigurableStartEnd = ConfigurableStart & ConfigurableEnd;
export interface InsertNodeOptions {
interface InsertNodeOptions {
/**
* Text to be inserted before the new node
*/
@ -83,16 +83,43 @@ namespace ts.textChanges {
delta?: number;
}
export type ChangeNodeOptions = ConfigurableStartEnd & InsertNodeOptions;
enum ChangeKind {
Remove,
ReplaceWithSingleNode,
ReplaceWithMultipleNodes
}
interface Change {
type Change = ReplaceWithSingleNode | ReplaceWithMultipleNodes | RemoveNode;
interface BaseChange {
readonly sourceFile: SourceFile;
readonly range: TextRange;
}
interface ChangeNodeOptions extends ConfigurableStartEnd, InsertNodeOptions {
readonly useIndentationFromFile?: boolean;
readonly node?: Node;
}
interface ReplaceWithSingleNode extends BaseChange {
readonly kind: ChangeKind.ReplaceWithSingleNode;
readonly node: Node;
readonly options?: ChangeNodeOptions;
}
interface RemoveNode extends BaseChange {
readonly kind: ChangeKind.Remove;
readonly node?: never;
readonly options?: never;
}
interface ChangeMultipleNodesOptions extends ChangeNodeOptions {
nodeSeparator: string;
}
interface ReplaceWithMultipleNodes extends BaseChange {
readonly kind: ChangeKind.ReplaceWithMultipleNodes;
readonly nodes: ReadonlyArray<Node>;
readonly options?: ChangeMultipleNodesOptions;
}
export function getSeparatorCharacter(separator: Token<SyntaxKind.CommaToken | SyntaxKind.SemicolonToken>) {
return tokenToString(separator.kind);
}
@ -153,12 +180,16 @@ 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 fromCodeFixContext(context: { newLineCharacter: string, rulesProvider: formatting.RulesProvider }) {
return new ChangeTracker(context.newLineCharacter === "\n" ? NewLineKind.LineFeed : NewLineKind.CarriageReturnLineFeed, context.rulesProvider);
public static fromCodeFixContext(context: { newLineCharacter: string, rulesProvider?: formatting.RulesProvider }) {
return new ChangeTracker(getNewlineKind(context), context.rulesProvider);
}
constructor(
@ -168,22 +199,22 @@ namespace ts.textChanges {
this.newLineCharacter = getNewLineCharacter({ newLine });
}
public deleteNode(sourceFile: SourceFile, node: Node, options: ConfigurableStartEnd = {}) {
const startPosition = getAdjustedStartPosition(sourceFile, node, options, Position.FullStart);
const endPosition = getAdjustedEndPosition(sourceFile, node, options);
this.changes.push({ sourceFile, options, range: { pos: startPosition, end: endPosition } });
public deleteRange(sourceFile: SourceFile, range: TextRange) {
this.changes.push({ kind: ChangeKind.Remove, sourceFile, range });
return this;
}
public deleteRange(sourceFile: SourceFile, range: TextRange) {
this.changes.push({ sourceFile, range });
public deleteNode(sourceFile: SourceFile, node: Node, options: ConfigurableStartEnd = {}) {
const startPosition = getAdjustedStartPosition(sourceFile, node, options, Position.FullStart);
const endPosition = getAdjustedEndPosition(sourceFile, node, options);
this.changes.push({ kind: ChangeKind.Remove, sourceFile, range: { pos: startPosition, end: endPosition } });
return this;
}
public deleteNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, options: ConfigurableStartEnd = {}) {
const startPosition = getAdjustedStartPosition(sourceFile, startNode, options, Position.FullStart);
const endPosition = getAdjustedEndPosition(sourceFile, endNode, options);
this.changes.push({ sourceFile, options, range: { pos: startPosition, end: endPosition } });
this.changes.push({ kind: ChangeKind.Remove, sourceFile, range: { pos: startPosition, end: endPosition } });
return this;
}
@ -223,33 +254,74 @@ namespace ts.textChanges {
}
public replaceRange(sourceFile: SourceFile, range: TextRange, newNode: Node, options: InsertNodeOptions = {}) {
this.changes.push({ sourceFile, range, options, node: newNode });
this.changes.push({ kind: ChangeKind.ReplaceWithSingleNode, sourceFile, range, options, node: newNode });
return this;
}
public replaceNode(sourceFile: SourceFile, oldNode: Node, newNode: Node, options: ChangeNodeOptions = {}) {
const startPosition = getAdjustedStartPosition(sourceFile, oldNode, options, Position.Start);
const endPosition = getAdjustedEndPosition(sourceFile, oldNode, options);
this.changes.push({ sourceFile, options, useIndentationFromFile: true, node: newNode, range: { pos: startPosition, end: endPosition } });
return this;
return this.replaceWithSingle(sourceFile, startPosition, endPosition, newNode, options);
}
public replaceNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, newNode: Node, options: ChangeNodeOptions = {}) {
const startPosition = getAdjustedStartPosition(sourceFile, startNode, options, Position.Start);
const endPosition = getAdjustedEndPosition(sourceFile, endNode, options);
this.changes.push({ sourceFile, options, useIndentationFromFile: true, node: newNode, range: { pos: startPosition, end: endPosition } });
return this.replaceWithSingle(sourceFile, startPosition, endPosition, newNode, options);
}
private replaceWithSingle(sourceFile: SourceFile, startPosition: number, endPosition: number, newNode: Node, options: ChangeNodeOptions): this {
this.changes.push({
kind: ChangeKind.ReplaceWithSingleNode,
sourceFile,
options,
node: newNode,
range: { pos: startPosition, end: endPosition }
});
return this;
}
private replaceWithMultiple(sourceFile: SourceFile, startPosition: number, endPosition: number, newNodes: ReadonlyArray<Node>, options: ChangeMultipleNodesOptions): this {
this.changes.push({
kind: ChangeKind.ReplaceWithMultipleNodes,
sourceFile,
options,
nodes: newNodes,
range: { pos: startPosition, end: endPosition }
});
return this;
}
public replaceNodeWithNodes(sourceFile: SourceFile, oldNode: Node, newNodes: ReadonlyArray<Node>, options: ChangeMultipleNodesOptions) {
const startPosition = getAdjustedStartPosition(sourceFile, oldNode, options, Position.Start);
const endPosition = getAdjustedEndPosition(sourceFile, oldNode, options);
return this.replaceWithMultiple(sourceFile, startPosition, endPosition, newNodes, options);
}
public replaceNodesWithNodes(sourceFile: SourceFile, oldNodes: ReadonlyArray<Node>, newNodes: ReadonlyArray<Node>, options: ChangeMultipleNodesOptions) {
const startPosition = getAdjustedStartPosition(sourceFile, oldNodes[0], options, Position.Start);
const endPosition = getAdjustedEndPosition(sourceFile, lastOrUndefined(oldNodes), options);
return this.replaceWithMultiple(sourceFile, startPosition, endPosition, newNodes, options);
}
public replaceRangeWithNodes(sourceFile: SourceFile, range: TextRange, newNodes: ReadonlyArray<Node>, options: ChangeMultipleNodesOptions) {
return this.replaceWithMultiple(sourceFile, range.pos, range.end, newNodes, options);
}
public replaceNodeRangeWithNodes(sourceFile: SourceFile, startNode: Node, endNode: Node, newNodes: ReadonlyArray<Node>, options: ChangeMultipleNodesOptions) {
const startPosition = getAdjustedStartPosition(sourceFile, startNode, options, Position.Start);
const endPosition = getAdjustedEndPosition(sourceFile, endNode, options);
return this.replaceWithMultiple(sourceFile, startPosition, endPosition, newNodes, options);
}
public insertNodeAt(sourceFile: SourceFile, pos: number, newNode: Node, options: InsertNodeOptions = {}) {
this.changes.push({ sourceFile, options, node: newNode, range: { pos, end: pos } });
this.changes.push({ kind: ChangeKind.ReplaceWithSingleNode, sourceFile, options, node: newNode, range: { pos, end: pos } });
return this;
}
public insertNodeBefore(sourceFile: SourceFile, before: Node, newNode: Node, options: InsertNodeOptions & ConfigurableStart = {}) {
const startPosition = getAdjustedStartPosition(sourceFile, before, options, Position.Start);
this.changes.push({ sourceFile, options, useIndentationFromFile: true, node: newNode, range: { pos: startPosition, end: startPosition } });
return this;
return this.replaceWithSingle(sourceFile, startPosition, startPosition, newNode, options);
}
public insertNodeAfter(sourceFile: SourceFile, after: Node, newNode: Node, options: InsertNodeOptions & ConfigurableEnd = {}) {
@ -261,6 +333,7 @@ namespace ts.textChanges {
// if not - insert semicolon to preserve the code from changing the meaning due to ASI
if (sourceFile.text.charCodeAt(after.end - 1) !== CharacterCodes.semicolon) {
this.changes.push({
kind: ChangeKind.ReplaceWithSingleNode,
sourceFile,
options: {},
range: { pos: after.end, end: after.end },
@ -269,8 +342,7 @@ namespace ts.textChanges {
}
}
const endPosition = getAdjustedEndPosition(sourceFile, after, options);
this.changes.push({ sourceFile, options, useIndentationFromFile: true, node: newNode, range: { pos: endPosition, end: endPosition } });
return this;
return this.replaceWithSingle(sourceFile, endPosition, endPosition, newNode, options);
}
/**
@ -339,10 +411,10 @@ namespace ts.textChanges {
}
this.changes.push({
kind: ChangeKind.ReplaceWithSingleNode,
sourceFile,
range: { pos: startPos, end: containingList[index + 1].getStart(sourceFile) },
node: newNode,
useIndentationFromFile: true,
options: {
prefix,
// write separator and leading trivia of the next element as suffix
@ -383,6 +455,7 @@ namespace ts.textChanges {
if (multilineList) {
// insert separator immediately following the 'after' node to preserve comments in trailing trivia
this.changes.push({
kind: ChangeKind.ReplaceWithSingleNode,
sourceFile,
range: { pos: end, end },
node: createToken(separator),
@ -396,6 +469,7 @@ namespace ts.textChanges {
insertPos--;
}
this.changes.push({
kind: ChangeKind.ReplaceWithSingleNode,
sourceFile,
range: { pos: insertPos, end: insertPos },
node: newNode,
@ -404,6 +478,7 @@ namespace ts.textChanges {
}
else {
this.changes.push({
kind: ChangeKind.ReplaceWithSingleNode,
sourceFile,
range: { pos: end, end },
node: newNode,
@ -446,38 +521,51 @@ namespace ts.textChanges {
}
private computeNewText(change: Change, sourceFile: SourceFile): string {
if (!change.node) {
if (change.kind === ChangeKind.Remove) {
// deletion case
return "";
}
const options = change.options || {};
const nonFormattedText = getNonformattedText(change.node, sourceFile, this.newLine);
let text: string;
const pos = change.range.pos;
const posStartsLine = getLineStartPositionForPosition(pos, sourceFile) === pos;
if (change.kind === ChangeKind.ReplaceWithMultipleNodes) {
const parts = change.nodes.map(n => this.getFormattedTextOfNode(n, sourceFile, pos, options));
text = parts.join(change.options.nodeSeparator);
}
else {
Debug.assert(change.kind === ChangeKind.ReplaceWithSingleNode, "change.kind === ReplaceWithSingleNode");
text = this.getFormattedTextOfNode(change.node, sourceFile, pos, options);
}
// strip initial indentation (spaces or tabs) if text will be inserted in the middle of the line
text = (posStartsLine || options.indentation !== undefined) ? text : text.replace(/^\s+/, "");
return (options.prefix || "") + text + (options.suffix || "");
}
private getFormattedTextOfNode(node: Node, sourceFile: SourceFile, pos: number, options: ChangeNodeOptions): string {
const nonformattedText = getNonformattedText(node, sourceFile, this.newLine);
if (this.validator) {
this.validator(nonFormattedText);
this.validator(nonformattedText);
}
const formatOptions = this.rulesProvider.getFormatOptions();
const pos = change.range.pos;
const posStartsLine = getLineStartPositionForPosition(pos, sourceFile) === pos;
const initialIndentation =
change.options.indentation !== undefined
? change.options.indentation
: change.useIndentationFromFile
? formatting.SmartIndenter.getIndentation(change.range.pos, sourceFile, formatOptions, posStartsLine || (change.options.prefix === this.newLineCharacter))
options.indentation !== undefined
? options.indentation
: (options.useIndentationFromFile !== false)
? formatting.SmartIndenter.getIndentation(pos, sourceFile, formatOptions, posStartsLine || (options.prefix === this.newLineCharacter))
: 0;
const delta =
change.options.delta !== undefined
? change.options.delta
: formatting.SmartIndenter.shouldIndentChildNode(change.node)
? formatOptions.indentSize
options.delta !== undefined
? options.delta
: formatting.SmartIndenter.shouldIndentChildNode(node)
? (formatOptions.indentSize || 0)
: 0;
let text = applyFormatting(nonFormattedText, sourceFile, initialIndentation, delta, this.rulesProvider);
// strip initial indentation (spaces or tabs) if text will be inserted in the middle of the line
// however keep indentation if it is was forced
text = posStartsLine || change.options.indentation !== undefined ? text : text.replace(/^\s+/, "");
return (options.prefix || "") + text + (options.suffix || "");
return applyFormatting(nonformattedText, sourceFile, initialIndentation, delta, this.rulesProvider);
}
private static normalize(changes: Change[]): Change[] {
@ -654,4 +742,4 @@ namespace ts.textChanges {
this.lastNonTriviaPosition = 0;
}
}
}
}

View file

@ -271,7 +271,6 @@ namespace ts {
isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean;
getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeAction[];
getApplicableRefactors(fileName: string, positionOrRaneg: number | TextRange): ApplicableRefactorInfo[];
getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string): RefactorEditInfo | undefined;

View file

@ -0,0 +1,98 @@
==ORIGINAL==
namespace A {
let x = 1;
function foo() {
}
namespace B {
function a() {
let a = 1;
let y = 5;
let z = x;
a = y;
foo();
}
}
}
==SCOPE::function a==
namespace A {
let x = 1;
function foo() {
}
namespace B {
function a() {
let a = 1;
newFunction();
function newFunction() {
let y = 5;
let z = x;
a = y;
foo();
}
}
}
}
==SCOPE::namespace B==
namespace A {
let x = 1;
function foo() {
}
namespace B {
function a() {
let a = 1;
a = newFunction(a);
}
function newFunction(a: number) {
let y = 5;
let z = x;
a = y;
foo();
return a;
}
}
}
==SCOPE::namespace A==
namespace A {
let x = 1;
function foo() {
}
namespace B {
function a() {
let a = 1;
a = newFunction(a);
}
}
function newFunction(a: number) {
let y = 5;
let z = x;
a = y;
foo();
return a;
}
}
==SCOPE::file '/a.ts'==
namespace A {
let x = 1;
function foo() {
}
namespace B {
function a() {
let a = 1;
a = newFunction(x, a, foo);
}
}
}
function newFunction(x: number, a: number, foo: () => void) {
let y = 5;
let z = x;
a = y;
foo();
return a;
}

View file

@ -0,0 +1,55 @@
==ORIGINAL==
namespace A {
export interface I { x: number };
class C {
a() {
let z = 1;
let a1: I = { x: 1 };
return a1.x + 10;
}
}
}
==SCOPE::class C==
namespace A {
export interface I { x: number };
class C {
a() {
let z = 1;
return this.newFunction();
}
private newFunction() {
let a1: I = { x: 1 };
return a1.x + 10;
}
}
}
==SCOPE::namespace A==
namespace A {
export interface I { x: number };
class C {
a() {
let z = 1;
return newFunction();
}
}
function newFunction() {
let a1: I = { x: 1 };
return a1.x + 10;
}
}
==SCOPE::file '/a.ts'==
namespace A {
export interface I { x: number };
class C {
a() {
let z = 1;
return newFunction();
}
}
}
function newFunction() {
let a1: A.I = { x: 1 };
return a1.x + 10;
}

View file

@ -0,0 +1,69 @@
==ORIGINAL==
namespace A {
let y = 1;
class C {
a() {
let z = 1;
let a1 = { x: 1 };
y = 10;
z = 42;
return a1.x + 10;
}
}
}
==SCOPE::class C==
namespace A {
let y = 1;
class C {
a() {
let z = 1;
var __return: any;
({ __return, z } = this.newFunction(z));
return __return;
}
private newFunction(z: number) {
let a1 = { x: 1 };
y = 10;
z = 42;
return { __return: a1.x + 10, z };
}
}
}
==SCOPE::namespace A==
namespace A {
let y = 1;
class C {
a() {
let z = 1;
var __return: any;
({ __return, z } = newFunction(z));
return __return;
}
}
function newFunction(z: number) {
let a1 = { x: 1 };
y = 10;
z = 42;
return { __return: a1.x + 10, z };
}
}
==SCOPE::file '/a.ts'==
namespace A {
let y = 1;
class C {
a() {
let z = 1;
var __return: any;
({ __return, y, z } = newFunction(y, z));
return __return;
}
}
}
function newFunction(y: number, z: number) {
let a1 = { x: 1 };
y = 10;
z = 42;
return { __return: a1.x + 10, y, z };
}

View file

@ -0,0 +1,36 @@
==ORIGINAL==
namespace A {
let y = 1;
class C {
b() {}
a() {
let z = 1;
let a1 = { x: 1 };
y = 10;
z = 42;
this.b();
return a1.x + 10;
}
}
}
==SCOPE::class C==
namespace A {
let y = 1;
class C {
b() {}
a() {
let z = 1;
var __return: any;
({ __return, z } = this.newFunction(z));
return __return;
}
private newFunction(z: number) {
let a1 = { x: 1 };
y = 10;
z = 42;
this.b();
return { __return: a1.x + 10, z };
}
}
}

View file

@ -0,0 +1,85 @@
==ORIGINAL==
namespace A {
let x = 1;
function foo() {
}
namespace B {
function a() {
let y = 5;
let z = x;
return foo();
}
}
}
==SCOPE::function a==
namespace A {
let x = 1;
function foo() {
}
namespace B {
function a() {
return newFunction();
function newFunction() {
let y = 5;
let z = x;
return foo();
}
}
}
}
==SCOPE::namespace B==
namespace A {
let x = 1;
function foo() {
}
namespace B {
function a() {
return newFunction();
}
function newFunction() {
let y = 5;
let z = x;
return foo();
}
}
}
==SCOPE::namespace A==
namespace A {
let x = 1;
function foo() {
}
namespace B {
function a() {
return newFunction();
}
}
function newFunction() {
let y = 5;
let z = x;
return foo();
}
}
==SCOPE::file '/a.ts'==
namespace A {
let x = 1;
function foo() {
}
namespace B {
function a() {
return newFunction(x, foo);
}
}
}
function newFunction(x: number, foo: () => void) {
let y = 5;
let z = x;
return foo();
}

View file

@ -0,0 +1,80 @@
==ORIGINAL==
namespace A {
function foo() {
}
namespace B {
function* a(z: number) {
let y = 5;
yield z;
return foo();
}
}
}
==SCOPE::function a==
namespace A {
function foo() {
}
namespace B {
function* a(z: number) {
return yield* newFunction();
function* newFunction() {
let y = 5;
yield z;
return foo();
}
}
}
}
==SCOPE::namespace B==
namespace A {
function foo() {
}
namespace B {
function* a(z: number) {
return yield* newFunction(z);
}
function* newFunction(z: number) {
let y = 5;
yield z;
return foo();
}
}
}
==SCOPE::namespace A==
namespace A {
function foo() {
}
namespace B {
function* a(z: number) {
return yield* newFunction(z);
}
}
function* newFunction(z: number) {
let y = 5;
yield z;
return foo();
}
}
==SCOPE::file '/a.ts'==
namespace A {
function foo() {
}
namespace B {
function* a(z: number) {
return yield* newFunction(z, foo);
}
}
}
function* newFunction(z: number, foo: () => void) {
let y = 5;
yield z;
return foo();
}

View file

@ -0,0 +1,90 @@
==ORIGINAL==
namespace A {
function foo() {
}
namespace B {
async function a(z: number, z1: any) {
let y = 5;
if (z) {
await z1;
}
return foo();
}
}
}
==SCOPE::function a==
namespace A {
function foo() {
}
namespace B {
async function a(z: number, z1: any) {
return await newFunction();
async function newFunction() {
let y = 5;
if(z) {
await z1;
}
return foo();
}
}
}
}
==SCOPE::namespace B==
namespace A {
function foo() {
}
namespace B {
async function a(z: number, z1: any) {
return await newFunction(z, z1);
}
async function newFunction(z: number, z1: any) {
let y = 5;
if(z) {
await z1;
}
return foo();
}
}
}
==SCOPE::namespace A==
namespace A {
function foo() {
}
namespace B {
async function a(z: number, z1: any) {
return await newFunction(z, z1);
}
}
async function newFunction(z: number, z1: any) {
let y = 5;
if(z) {
await z1;
}
return foo();
}
}
==SCOPE::file '/a.ts'==
namespace A {
function foo() {
}
namespace B {
async function a(z: number, z1: any) {
return await newFunction(z, z1, foo);
}
}
}
async function newFunction(z: number, z1: any, foo: () => void) {
let y = 5;
if(z) {
await z1;
}
return foo();
}

View file

@ -0,0 +1,98 @@
==ORIGINAL==
namespace A {
let x = 1;
export function foo() {
}
namespace B {
function a() {
let a = 1;
let y = 5;
let z = x;
a = y;
foo();
}
}
}
==SCOPE::function a==
namespace A {
let x = 1;
export function foo() {
}
namespace B {
function a() {
let a = 1;
newFunction();
function newFunction() {
let y = 5;
let z = x;
a = y;
foo();
}
}
}
}
==SCOPE::namespace B==
namespace A {
let x = 1;
export function foo() {
}
namespace B {
function a() {
let a = 1;
a = newFunction(a);
}
function newFunction(a: number) {
let y = 5;
let z = x;
a = y;
foo();
return a;
}
}
}
==SCOPE::namespace A==
namespace A {
let x = 1;
export function foo() {
}
namespace B {
function a() {
let a = 1;
a = newFunction(a);
}
}
function newFunction(a: number) {
let y = 5;
let z = x;
a = y;
foo();
return a;
}
}
==SCOPE::file '/a.ts'==
namespace A {
let x = 1;
export function foo() {
}
namespace B {
function a() {
let a = 1;
a = newFunction(x, a);
}
}
}
function newFunction(x: number, a: number) {
let y = 5;
let z = x;
a = y;
A.foo();
return a;
}

View file

@ -0,0 +1,101 @@
==ORIGINAL==
namespace A {
let x = 1;
export function foo() {
}
namespace B {
function a() {
let a = 1;
let y = 5;
let z = x;
a = y;
return foo();
}
}
}
==SCOPE::function a==
namespace A {
let x = 1;
export function foo() {
}
namespace B {
function a() {
let a = 1;
return newFunction();
function newFunction() {
let y = 5;
let z = x;
a = y;
return foo();
}
}
}
}
==SCOPE::namespace B==
namespace A {
let x = 1;
export function foo() {
}
namespace B {
function a() {
let a = 1;
var __return: any;
({ __return, a } = newFunction(a));
return __return;
}
function newFunction(a: number) {
let y = 5;
let z = x;
a = y;
return { __return: foo(), a };
}
}
}
==SCOPE::namespace A==
namespace A {
let x = 1;
export function foo() {
}
namespace B {
function a() {
let a = 1;
var __return: any;
({ __return, a } = newFunction(a));
return __return;
}
}
function newFunction(a: number) {
let y = 5;
let z = x;
a = y;
return { __return: foo(), a };
}
}
==SCOPE::file '/a.ts'==
namespace A {
let x = 1;
export function foo() {
}
namespace B {
function a() {
let a = 1;
var __return: any;
({ __return, a } = newFunction(x, a));
return __return;
}
}
}
function newFunction(x: number, a: number) {
let y = 5;
let z = x;
a = y;
return { __return: A.foo(), a };
}

View file

@ -0,0 +1,111 @@
==ORIGINAL==
namespace A {
let x = 1;
export namespace C {
export function foo() {
}
}
namespace B {
function a() {
let a = 1;
let y = 5;
let z = x;
a = y;
return C.foo();
}
}
}
==SCOPE::function a==
namespace A {
let x = 1;
export namespace C {
export function foo() {
}
}
namespace B {
function a() {
let a = 1;
return newFunction();
function newFunction() {
let y = 5;
let z = x;
a = y;
return C.foo();
}
}
}
}
==SCOPE::namespace B==
namespace A {
let x = 1;
export namespace C {
export function foo() {
}
}
namespace B {
function a() {
let a = 1;
var __return: any;
({ __return, a } = newFunction(a));
return __return;
}
function newFunction(a: number) {
let y = 5;
let z = x;
a = y;
return { __return: C.foo(), a };
}
}
}
==SCOPE::namespace A==
namespace A {
let x = 1;
export namespace C {
export function foo() {
}
}
namespace B {
function a() {
let a = 1;
var __return: any;
({ __return, a } = newFunction(a));
return __return;
}
}
function newFunction(a: number) {
let y = 5;
let z = x;
a = y;
return { __return: C.foo(), a };
}
}
==SCOPE::file '/a.ts'==
namespace A {
let x = 1;
export namespace C {
export function foo() {
}
}
namespace B {
function a() {
let a = 1;
var __return: any;
({ __return, a } = newFunction(x, a));
return __return;
}
}
}
function newFunction(x: number, a: number) {
let y = 5;
let z = x;
a = y;
return { __return: A.C.foo(), a };
}

View file

@ -0,0 +1,65 @@
==ORIGINAL==
namespace A {
let x = 1;
namespace B {
function a() {
let a1 = 1;
return 1 + a1 + x + 100;
}
}
}
==SCOPE::function a==
namespace A {
let x = 1;
namespace B {
function a() {
let a1 = 1;
return newFunction() + 100;
function newFunction() {
return 1 + a1 + x;
}
}
}
}
==SCOPE::namespace B==
namespace A {
let x = 1;
namespace B {
function a() {
let a1 = 1;
return newFunction(a1) + 100;
}
function newFunction(a1: number) {
return 1 + a1 + x;
}
}
}
==SCOPE::namespace A==
namespace A {
let x = 1;
namespace B {
function a() {
let a1 = 1;
return newFunction(a1) + 100;
}
}
function newFunction(a1: number) {
return 1 + a1 + x;
}
}
==SCOPE::file '/a.ts'==
namespace A {
let x = 1;
namespace B {
function a() {
let a1 = 1;
return newFunction(a1, x) + 100;
}
}
}
function newFunction(a1: number, x: number) {
return 1 + a1 + x;
}

View file

@ -0,0 +1,65 @@
==ORIGINAL==
namespace A {
export interface I { x: number };
namespace B {
function a() {
let a1: I = { x: 1 };
return a1.x + 10;
}
}
}
==SCOPE::function a==
namespace A {
export interface I { x: number };
namespace B {
function a() {
return newFunction();
function newFunction() {
let a1: I = { x: 1 };
return a1.x + 10;
}
}
}
}
==SCOPE::namespace B==
namespace A {
export interface I { x: number };
namespace B {
function a() {
return newFunction();
}
function newFunction() {
let a1: I = { x: 1 };
return a1.x + 10;
}
}
}
==SCOPE::namespace A==
namespace A {
export interface I { x: number };
namespace B {
function a() {
return newFunction();
}
}
function newFunction() {
let a1: I = { x: 1 };
return a1.x + 10;
}
}
==SCOPE::file '/a.ts'==
namespace A {
export interface I { x: number };
namespace B {
function a() {
return newFunction();
}
}
}
function newFunction() {
let a1: A.I = { x: 1 };
return a1.x + 10;
}

View file

@ -0,0 +1,98 @@
==ORIGINAL==
namespace A {
let x = 1;
function foo() {
}
namespace B {
function a() {
let a = 1;
let y = 5;
let z = x;
a = y;
foo();
}
}
}
==SCOPE::function a==
namespace A {
let x = 1;
function foo() {
}
namespace B {
function a() {
let a = 1;
newFunction();
function newFunction() {
let y = 5;
let z = x;
a = y;
foo();
}
}
}
}
==SCOPE::namespace B==
namespace A {
let x = 1;
function foo() {
}
namespace B {
function a() {
let a = 1;
({ a } = newFunction(a));
}
function newFunction(a: any) {
let y = 5;
let z = x;
a = y;
foo();
return { a };
}
}
}
==SCOPE::namespace A==
namespace A {
let x = 1;
function foo() {
}
namespace B {
function a() {
let a = 1;
({ a } = newFunction(a));
}
}
function newFunction(a: any) {
let y = 5;
let z = x;
a = y;
foo();
return { a };
}
}
==SCOPE::file '/a.ts'==
namespace A {
let x = 1;
function foo() {
}
namespace B {
function a() {
let a = 1;
({ a } = newFunction(x, a, foo));
}
}
}
function newFunction(x: any, a: any, foo: any) {
let y = 5;
let z = x;
a = y;
foo();
return { a };
}

View file

@ -0,0 +1,70 @@
==ORIGINAL==
namespace A {
export interface I { x: number };
class C {
a() {
let z = 1;
let a1: I = { x: 1 };
return a1.x + 10;
}
}
}
==SCOPE::method a==
namespace A {
export interface I { x: number };
class C {
a() {
let z = 1;
return newFunction();
function newFunction() {
let a1: I = { x: 1 };
return a1.x + 10;
}
}
}
}
==SCOPE::class C==
namespace A {
export interface I { x: number };
class C {
a() {
let z = 1;
return this.newFunction();
}
private newFunction() {
let a1: I = { x: 1 };
return a1.x + 10;
}
}
}
==SCOPE::namespace A==
namespace A {
export interface I { x: number };
class C {
a() {
let z = 1;
return newFunction();
}
}
function newFunction() {
let a1: I = { x: 1 };
return a1.x + 10;
}
}
==SCOPE::file '/a.ts'==
namespace A {
export interface I { x: number };
class C {
a() {
let z = 1;
return newFunction();
}
}
}
function newFunction() {
let a1: A.I = { x: 1 };
return a1.x + 10;
}

View file

@ -0,0 +1,86 @@
==ORIGINAL==
namespace A {
let y = 1;
class C {
a() {
let z = 1;
let a1 = { x: 1 };
y = 10;
z = 42;
return a1.x + 10;
}
}
}
==SCOPE::method a==
namespace A {
let y = 1;
class C {
a() {
let z = 1;
return newFunction();
function newFunction() {
let a1 = { x: 1 };
y = 10;
z = 42;
return a1.x + 10;
}
}
}
}
==SCOPE::class C==
namespace A {
let y = 1;
class C {
a() {
let z = 1;
var __return: any;
({ z, __return } = this.newFunction(z));
return __return;
}
private newFunction(z: any) {
let a1 = { x: 1 };
y = 10;
z = 42;
return { z, __return: a1.x + 10 };
}
}
}
==SCOPE::namespace A==
namespace A {
let y = 1;
class C {
a() {
let z = 1;
var __return: any;
({ z, __return } = newFunction(z));
return __return;
}
}
function newFunction(z: any) {
let a1 = { x: 1 };
y = 10;
z = 42;
return { z, __return: a1.x + 10 };
}
}
==SCOPE::file '/a.ts'==
namespace A {
let y = 1;
class C {
a() {
let z = 1;
var __return: any;
({ y, z, __return } = newFunction(y, z));
return __return;
}
}
}
function newFunction(y: any, z: any) {
let a1 = { x: 1 };
y = 10;
z = 42;
return { y, z, __return: a1.x + 10 };
}

View file

@ -0,0 +1,36 @@
==ORIGINAL==
namespace A {
let y = 1;
class C {
b() {}
a() {
let z = 1;
let a1 = { x: 1 };
y = 10;
z = 42;
this.b();
return a1.x + 10;
}
}
}
==SCOPE::class C==
namespace A {
let y = 1;
class C {
b() {}
a() {
let z = 1;
var __return: any;
({ z, __return } = this.newFunction(z));
return __return;
}
private newFunction(z: any) {
let a1 = { x: 1 };
y = 10;
z = 42;
this.b();
return { z, __return: a1.x + 10 };
}
}
}

View file

@ -0,0 +1,85 @@
==ORIGINAL==
namespace A {
let x = 1;
function foo() {
}
namespace B {
function a() {
let y = 5;
let z = x;
return foo();
}
}
}
==SCOPE::function a==
namespace A {
let x = 1;
function foo() {
}
namespace B {
function a() {
return newFunction();
function newFunction() {
let y = 5;
let z = x;
return foo();
}
}
}
}
==SCOPE::namespace B==
namespace A {
let x = 1;
function foo() {
}
namespace B {
function a() {
return newFunction();
}
function newFunction() {
let y = 5;
let z = x;
return foo();
}
}
}
==SCOPE::namespace A==
namespace A {
let x = 1;
function foo() {
}
namespace B {
function a() {
return newFunction();
}
}
function newFunction() {
let y = 5;
let z = x;
return foo();
}
}
==SCOPE::file '/a.ts'==
namespace A {
let x = 1;
function foo() {
}
namespace B {
function a() {
return newFunction(x, foo);
}
}
}
function newFunction(x: any, foo: any) {
let y = 5;
let z = x;
return foo();
}

View file

@ -0,0 +1,80 @@
==ORIGINAL==
namespace A {
function foo() {
}
namespace B {
function* a(z: number) {
let y = 5;
yield z;
return foo();
}
}
}
==SCOPE::function a==
namespace A {
function foo() {
}
namespace B {
function* a(z: number) {
return yield* newFunction();
function* newFunction() {
let y = 5;
yield z;
return foo();
}
}
}
}
==SCOPE::namespace B==
namespace A {
function foo() {
}
namespace B {
function* a(z: number) {
return yield* newFunction(z);
}
function* newFunction(z: any) {
let y = 5;
yield z;
return foo();
}
}
}
==SCOPE::namespace A==
namespace A {
function foo() {
}
namespace B {
function* a(z: number) {
return yield* newFunction(z);
}
}
function* newFunction(z: any) {
let y = 5;
yield z;
return foo();
}
}
==SCOPE::file '/a.ts'==
namespace A {
function foo() {
}
namespace B {
function* a(z: number) {
return yield* newFunction(z, foo);
}
}
}
function* newFunction(z: any, foo: any) {
let y = 5;
yield z;
return foo();
}

View file

@ -0,0 +1,90 @@
==ORIGINAL==
namespace A {
function foo() {
}
namespace B {
async function a(z: number, z1: any) {
let y = 5;
if (z) {
await z1;
}
return foo();
}
}
}
==SCOPE::function a==
namespace A {
function foo() {
}
namespace B {
async function a(z: number, z1: any) {
return await newFunction();
async function newFunction() {
let y = 5;
if (z) {
await z1;
}
return foo();
}
}
}
}
==SCOPE::namespace B==
namespace A {
function foo() {
}
namespace B {
async function a(z: number, z1: any) {
return await newFunction(z, z1);
}
async function newFunction(z: any, z1: any) {
let y = 5;
if (z) {
await z1;
}
return foo();
}
}
}
==SCOPE::namespace A==
namespace A {
function foo() {
}
namespace B {
async function a(z: number, z1: any) {
return await newFunction(z, z1);
}
}
async function newFunction(z: any, z1: any) {
let y = 5;
if (z) {
await z1;
}
return foo();
}
}
==SCOPE::file '/a.ts'==
namespace A {
function foo() {
}
namespace B {
async function a(z: number, z1: any) {
return await newFunction(z, z1, foo);
}
}
}
async function newFunction(z: any, z1: any, foo: any) {
let y = 5;
if (z) {
await z1;
}
return foo();
}

View file

@ -0,0 +1,98 @@
==ORIGINAL==
namespace A {
let x = 1;
export function foo() {
}
namespace B {
function a() {
let a = 1;
let y = 5;
let z = x;
a = y;
foo();
}
}
}
==SCOPE::function a==
namespace A {
let x = 1;
export function foo() {
}
namespace B {
function a() {
let a = 1;
newFunction();
function newFunction() {
let y = 5;
let z = x;
a = y;
foo();
}
}
}
}
==SCOPE::namespace B==
namespace A {
let x = 1;
export function foo() {
}
namespace B {
function a() {
let a = 1;
({ a } = newFunction(a));
}
function newFunction(a: any) {
let y = 5;
let z = x;
a = y;
foo();
return { a };
}
}
}
==SCOPE::namespace A==
namespace A {
let x = 1;
export function foo() {
}
namespace B {
function a() {
let a = 1;
({ a } = newFunction(a));
}
}
function newFunction(a: any) {
let y = 5;
let z = x;
a = y;
foo();
return { a };
}
}
==SCOPE::file '/a.ts'==
namespace A {
let x = 1;
export function foo() {
}
namespace B {
function a() {
let a = 1;
({ a } = newFunction(x, a));
}
}
}
function newFunction(x: any, a: any) {
let y = 5;
let z = x;
a = y;
A.foo();
return { a };
}

View file

@ -0,0 +1,101 @@
==ORIGINAL==
namespace A {
let x = 1;
export function foo() {
}
namespace B {
function a() {
let a = 1;
let y = 5;
let z = x;
a = y;
return foo();
}
}
}
==SCOPE::function a==
namespace A {
let x = 1;
export function foo() {
}
namespace B {
function a() {
let a = 1;
return newFunction();
function newFunction() {
let y = 5;
let z = x;
a = y;
return foo();
}
}
}
}
==SCOPE::namespace B==
namespace A {
let x = 1;
export function foo() {
}
namespace B {
function a() {
let a = 1;
var __return: any;
({ a, __return } = newFunction(a));
return __return;
}
function newFunction(a: any) {
let y = 5;
let z = x;
a = y;
return { a, __return: foo() };
}
}
}
==SCOPE::namespace A==
namespace A {
let x = 1;
export function foo() {
}
namespace B {
function a() {
let a = 1;
var __return: any;
({ a, __return } = newFunction(a));
return __return;
}
}
function newFunction(a: any) {
let y = 5;
let z = x;
a = y;
return { a, __return: foo() };
}
}
==SCOPE::file '/a.ts'==
namespace A {
let x = 1;
export function foo() {
}
namespace B {
function a() {
let a = 1;
var __return: any;
({ a, __return } = newFunction(x, a));
return __return;
}
}
}
function newFunction(x: any, a: any) {
let y = 5;
let z = x;
a = y;
return { a, __return: A.foo() };
}

View file

@ -0,0 +1,111 @@
==ORIGINAL==
namespace A {
let x = 1;
export namespace C {
export function foo() {
}
}
namespace B {
function a() {
let a = 1;
let y = 5;
let z = x;
a = y;
return C.foo();
}
}
}
==SCOPE::function a==
namespace A {
let x = 1;
export namespace C {
export function foo() {
}
}
namespace B {
function a() {
let a = 1;
return newFunction();
function newFunction() {
let y = 5;
let z = x;
a = y;
return C.foo();
}
}
}
}
==SCOPE::namespace B==
namespace A {
let x = 1;
export namespace C {
export function foo() {
}
}
namespace B {
function a() {
let a = 1;
var __return: any;
({ a, __return } = newFunction(a));
return __return;
}
function newFunction(a: any) {
let y = 5;
let z = x;
a = y;
return { a, __return: C.foo() };
}
}
}
==SCOPE::namespace A==
namespace A {
let x = 1;
export namespace C {
export function foo() {
}
}
namespace B {
function a() {
let a = 1;
var __return: any;
({ a, __return } = newFunction(a));
return __return;
}
}
function newFunction(a: any) {
let y = 5;
let z = x;
a = y;
return { a, __return: C.foo() };
}
}
==SCOPE::file '/a.ts'==
namespace A {
let x = 1;
export namespace C {
export function foo() {
}
}
namespace B {
function a() {
let a = 1;
var __return: any;
({ a, __return } = newFunction(x, a));
return __return;
}
}
}
function newFunction(x: any, a: any) {
let y = 5;
let z = x;
a = y;
return { a, __return: A.C.foo() };
}

View file

@ -0,0 +1,57 @@
==ORIGINAL==
namespace A {
let x = 1;
namespace B {
function a() {
let a1 = 1;
return 1 + a1 + x + 100;
}
}
}
==SCOPE::function a==
namespace A {
let x = 1;
namespace B {
function a() {
let a1 = 1;
return newFunction() + 100;
function newFunction() { 1 + a1 + x; }
}
}
}
==SCOPE::namespace B==
namespace A {
let x = 1;
namespace B {
function a() {
let a1 = 1;
return newFunction(a1) + 100;
}
function newFunction(a1: any) { 1 + a1 + x; }
}
}
==SCOPE::namespace A==
namespace A {
let x = 1;
namespace B {
function a() {
let a1 = 1;
return newFunction(a1) + 100;
}
}
function newFunction(a1: any) { 1 + a1 + x; }
}
==SCOPE::file '/a.ts'==
namespace A {
let x = 1;
namespace B {
function a() {
let a1 = 1;
return newFunction(a1, x) + 100;
}
}
}
function newFunction(a1: any, x: any) { 1 + a1 + x; }

View file

@ -0,0 +1,65 @@
==ORIGINAL==
namespace A {
export interface I { x: number };
namespace B {
function a() {
let a1: I = { x: 1 };
return a1.x + 10;
}
}
}
==SCOPE::function a==
namespace A {
export interface I { x: number };
namespace B {
function a() {
return newFunction();
function newFunction() {
let a1: I = { x: 1 };
return a1.x + 10;
}
}
}
}
==SCOPE::namespace B==
namespace A {
export interface I { x: number };
namespace B {
function a() {
return newFunction();
}
function newFunction() {
let a1: I = { x: 1 };
return a1.x + 10;
}
}
}
==SCOPE::namespace A==
namespace A {
export interface I { x: number };
namespace B {
function a() {
return newFunction();
}
}
function newFunction() {
let a1: I = { x: 1 };
return a1.x + 10;
}
}
==SCOPE::file '/a.ts'==
namespace A {
export interface I { x: number };
namespace B {
function a() {
return newFunction();
}
}
}
function newFunction() {
let a1: A.I = { x: 1 };
return a1.x + 10;
}

View file

@ -47,7 +47,7 @@ verify.completionListIsGlobal(true);
goTo.marker("6");
verify.completionListIsGlobal(false);
goTo.marker("7");
verify.completionListIsGlobal(true);
verify.completionListIsGlobal(false);
goTo.marker("8");
verify.completionListIsGlobal(false);
goTo.marker("9");

View file

@ -0,0 +1,33 @@
/// <reference path='fourslash.ts' />
//// class Foo {
//// someMethod(m: number) {
//// /*start*/var x = m;
//// x = x * 3;
//// var y = 30;
//// var z = y + x;
//// console.log(z);/*end*/
//// var q = 10;
//// return q;
//// }
//// }
goTo.select('start', 'end')
verify.refactorAvailable('Extract Method');
edit.applyRefactor('Extract Method', "scope_0");
verify.currentFileContentIs(
`class Foo {
someMethod(m: number) {
this.newFunction(m);
var q = 10;
return q;
}
private newFunction(m: number) {
var x = m;
x = x * 3;
var y = 30;
var z = y + x;
console.log(z);
}
}`);

View file

@ -0,0 +1,6 @@
/// <reference path='fourslash.ts' />
//// (x => x)(/*1*/x => x/*2*/)(1);
goTo.select('1', '2');
edit.applyRefactor('Extract Method', 'scope_0');

View file

@ -0,0 +1,28 @@
/// <reference path='fourslash.ts' />
// Nonexhaustive list of things it should be illegal to be extract-method on
// * Import declarations
// * Super calls
// * Function body blocks
// * try/catch blocks
//// /*1a*/import * as x from 'y';/*1b*/
//// namespace N {
//// /*oka*/class C extends B {
//// constructor() {
//// /*2a*/super();/*2b*/
//// }
//// }/*okb*/
//// }
//// function f() /*3a*/{ return 0 }/*3b*/
//// try /*4a*/{ console.log }/*4b*/ catch (e) /*5a*/{ console.log; }/*5b*/
for (const m of ['1', '2', '3', '4', '5']) {
goTo.select(m + 'a', m + 'b');
verify.not.refactorAvailable('Extract Method');
}
// Verify we can still extract the entire class
goTo.select('oka', 'okb');
verify.refactorAvailable('Extract Method');

View file

@ -0,0 +1,30 @@
/// <reference path='fourslash.ts' />
// Extracting from a static context should make static methods.
// Also checks that we correctly find non-conflicting names in static contexts.
//// class C {
//// static j = /*c*/100/*d*/;
//// constructor(q: string = /*a*/"hello"/*b*/) {
//// }
//// }
goTo.select('a', 'b');
edit.applyRefactor('Extract Method', 'scope_0');
goTo.select('c', 'd');
edit.applyRefactor('Extract Method', 'scope_0');
verify.currentFileContentIs(`class C {
static j = C.newFunction_1();
constructor(q: string = C.newFunction()) {
}
private static newFunction(): string {
return "hello";
}
private static newFunction_1() {
return 100;
}
}`);

View file

@ -0,0 +1,24 @@
/// <reference path='fourslash.ts' />
// Don't emit type annotations in JavaScript files
// Also tests that single-variable return extractions don't get superfluous destructuring
// @allowNonTsExtensions: true
// @Filename: foo.js
//// function foo() {
//// var i = 10;
//// /*a*/return i++;/*b*/
//// }
goTo.select('a', 'b');
edit.applyRefactor('Extract Method', 'scope_1');
verify.currentFileContentIs(`function foo() {
var i = 10;
var __return: any;
({ __return, i } = newFunction(i));
return __return;
}
function newFunction(i) {
return { __return: i++, i };
}
`);

View file

@ -0,0 +1,22 @@
/// <reference path='fourslash.ts' />
// Extracting an increment expression (not statement) should do the right thing,
// including not generating extra destructuring unless needed
//// function foo() {
//// var i = 10;
//// /*a*/i++/*b*/;
//// }
goTo.select('a', 'b');
edit.applyRefactor('Extract Method', 'scope_1');
verify.currentFileContentIs(`function foo() {
var i = 10;
i = newFunction(i);
}
function newFunction(i: number) {
i++;
return i;
}
`);

View file

@ -0,0 +1,10 @@
/// <reference path='fourslash.ts' />
//// function foo () {
//// var x = 3;
//// var y = /*start*/x++ + 5/*end*/;
//// }
goTo.select('start', 'end')
verify.refactorAvailable('Extract Method', 'scope_0');
verify.not.refactorAvailable('Extract Method', 'scope_1');

View file

@ -0,0 +1,21 @@
/// <reference path='fourslash.ts' />
// Don't try to propagate property accessed variables back,
// or emit spurious returns when the value is clearly ignored
//// function fn() {
//// const x = { m: 1 };
//// /*a*/x.m = 3/*b*/;
//// }
goTo.select('a', 'b')
verify.refactorAvailable('Extract Method');
edit.applyRefactor('Extract Method', "scope_1");
verify.currentFileContentIs(`function fn() {
const x = { m: 1 };
newFunction(x);
}
function newFunction(x: { m: number; }) {
x.m = 3;
}
`);

View file

@ -0,0 +1,22 @@
/// <reference path='fourslash.ts' />
// New function names should be totally new to the file
//// function fn() {
//// /*a*/console.log("hi");/*b*/
//// }
////
//// function newFunction() { }
goTo.select('a', 'b')
verify.refactorAvailable('Extract Method');
edit.applyRefactor('Extract Method', "scope_0");
verify.currentFileContentIs(`function fn() {
newFunction_1();
function newFunction_1() {
console.log("hi");
}
}
function newFunction() { }`);

View file

@ -0,0 +1,28 @@
/// <reference path='fourslash.ts' />
//// namespace NS {
//// class Q {
//// foo() {
//// console.log('100');
//// const m = 10, j = "hello", k = {x: "what"};
//// const q = /*start*/m + j + k/*end*/;
//// }
//// }
//// }
goTo.select('start', 'end')
verify.refactorAvailable('Extract Method');
edit.applyRefactor('Extract Method', "scope_2");
verify.currentFileContentIs(
`namespace NS {
class Q {
foo() {
console.log('100');
const m = 10, j = "hello", k = {x: "what"};
const q = newFunction(m, j, k);
}
}
}
function newFunction(m: number, j: string, k: { x: string; }) {
return m + j + k;
}
`);

View file

@ -0,0 +1,14 @@
/// <reference path='fourslash.ts' />
// Shouldn't be able to extract a readonly property initializer outside the constructor
//// class Foo {
//// readonly prop;
//// constructor() {
//// /*a*/this.prop = 10;/*b*/
//// }
//// }
goTo.select('a', 'b')
verify.refactorAvailable('Extract Method', 'scope_0');
verify.not.refactorAvailable('Extract Method', 'scope_1');

View file

@ -0,0 +1,25 @@
/// <reference path='fourslash.ts' />
// Extracting from a static method should create a static method
//// class Foo {
//// static method() {
//// /*start*/return 1;/*end*/
//// }
//// }
goTo.select('start', 'end')
verify.refactorAvailable('Extract Method');
edit.applyRefactor('Extract Method', "scope_0");
verify.currentFileContentIs(`class Foo {
static method() {
return Foo.newFunction();
}
private static newFunction() {
return 1;
}
}`);

View file

@ -0,0 +1,10 @@
/// <reference path='fourslash.ts' />
// You may not extract variable declarations with the export modifier
//// namespace NS {
//// /*start*/export var x = 10;/*end*/
//// }
goTo.select('start', 'end')
verify.not.refactorAvailable('Extract Method');

View file

@ -0,0 +1,8 @@
/// <reference path='fourslash.ts' />
//// declare namespace Foo {
//// const x = /*start*/3/*end*/;
//// }
goTo.select('start', 'end')
verify.not.refactorAvailable('Extract Method');

View file

@ -0,0 +1,19 @@
/// <reference path='fourslash.ts' />
//// function M() {
//// let a = [1,2,3];
//// let x = 0;
//// console.log(/*a*/a[x]/*b*/);
//// }
goTo.select('a', 'b')
edit.applyRefactor('Extract Method', 'scope_1');
verify.currentFileContentIs(`function M() {
let a = [1,2,3];
let x = 0;
console.log(newFunction(a, x));
}
function newFunction(a: number[], x: number): any {
return a[x];
}
`);

View file

@ -0,0 +1,18 @@
/// <reference path='fourslash.ts' />
//// namespace NS {
//// class Q {
//// foo() {
//// console.log('100');
//// const m = 10, j = "hello", k = {x: "what"};
//// const q = /*a*/m/*b*/;
//// }
//// }
//// }
// Don't offer to to 'extract method' a single identifier
goTo.marker('a');
verify.not.refactorAvailable('Extract Method');
goTo.select('a', 'b');
verify.not.refactorAvailable('Extract Method');

View file

@ -0,0 +1,14 @@
/// <reference path='fourslash.ts' />
//// let a = 1, b = 2, c = 3, d = 4;
//// namespace NS {
//// class Q {
//// foo() {
//// a = /*1*/b = c/*2*/ = d;
//// }
//// }
//// }
// Should rewrite to a = newFunc(); function() { return b = c = d; }
goTo.select('1', '2');
verify.not.refactorAvailable('Extract Method');

View file

@ -0,0 +1,20 @@
/// <reference path='fourslash.ts' />
// Extraction in the context of a contextual
// type needs to produce an explicit return type
// annotation in the extracted function
//// function f() {
//// var x: 1 | 2 | 3 = /*start*/2/*end*/;
//// }
goTo.select('start', 'end');
edit.applyRefactor('Extract Method', 'scope_0');
verify.currentFileContentIs(
`function f() {
var x: 1 | 2 | 3 = newFunction();
function newFunction(): 1 | 2 | 3 {
return 2;
}
}`);

View file

@ -0,0 +1,16 @@
/// <reference path='fourslash.ts' />
// Cannot extract globally-declared functions or
// those with non-selected local references
//// /*f1a*/function f() {
//// /*g1a*/function g() { }
//// g();/*g1b*/
//// g();
//// }/*f1b*/
goTo.select('f1a', 'f1b');
verify.not.refactorAvailable('Extract Method');
goTo.select('g1a', 'g1b');
verify.not.refactorAvailable('Extract Method');

View file

@ -0,0 +1,16 @@
/// <reference path='fourslash.ts' />
// You cannot extract a function initializer into the function's body.
// The innermost scope (scope_0) is the sibling of the function, not the function itself.
//// function fn(x = /*a*/3/*b*/) {
//// }
goTo.select('a', 'b');
edit.applyRefactor('Extract Method', 'scope_0');
verify.currentFileContentIs(`function fn(x = newFunction()) {
}
function newFunction() {
return 3;
}
`);

View file

@ -0,0 +1,17 @@
/// <reference path='fourslash.ts' />
// You cannot extract an exported function declaration
//// namespace ns {
//// /*a*/export function fn() {
////
//// }
//// fn();
//// /*b*/
//// }
goTo.select('a', 'b');
verify.not.refactorAvailable("Extract Method");
edit.deleteAtCaret('export'.length);
goTo.select('a', 'b');
verify.refactorAvailable("Extract Method");

View file

@ -0,0 +1,11 @@
/// <reference path='fourslash.ts' />
//// function f() {
//// /*a*/function q() { }
//// q();/*b*/
//// q();
//// }
goTo.select('a', 'b');
verify.not.refactorAvailable("Extract Method");

View file

@ -130,6 +130,7 @@ declare namespace FourSlashInterface {
position(position: number, fileName?: string): any;
file(index: number, content?: string, scriptKindName?: string): any;
file(name: string, content?: string, scriptKindName?: string): any;
select(startMarker: string, endMarker: string): void;
}
class verifyNegatable {
private negative;
@ -156,6 +157,8 @@ declare namespace FourSlashInterface {
applicableRefactorAvailableAtMarker(markerName: string): void;
codeFixDiagnosticsAvailableAtMarkers(markerNames: string[], diagnosticCode?: number): void;
applicableRefactorAvailableForRange(): void;
refactorAvailable(name?: string, subName?: string);
}
class verify extends verifyNegatable {
assertHasRanges(ranges: Range[]): void;
@ -305,6 +308,8 @@ declare namespace FourSlashInterface {
moveLeft(count?: number): void;
enableFormatting(): void;
disableFormatting(): void;
applyRefactor(refactorName: string, actionName: string): void;
}
class debug {
printCurrentParameterHelp(): void;