Skip to content

Commit c7f665f

Browse files
committed
Extract Method (squash)
1 parent 8f7a582 commit c7f665f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+4367
-77
lines changed

.vscode/tasks.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@
1818
"problemMatcher": [
1919
"$tsc"
2020
]
21+
},
22+
{
23+
"taskName": "tests",
24+
"showOutput": "silent",
25+
"problemMatcher": [
26+
"$tsc"
27+
]
2128
}
2229
]
2330
}

Jakefile.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ var harnessSources = harnessCoreSources.concat([
133133
"projectErrors.ts",
134134
"matchFiles.ts",
135135
"initializeTSConfig.ts",
136+
"extractMethods.ts",
136137
"printer.ts",
137138
"textChanges.ts",
138139
"telemetry.ts",

src/compiler/checker.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -223,11 +223,10 @@ namespace ts {
223223
getSuggestionForNonexistentProperty: (node, type) => unescapeLeadingUnderscores(getSuggestionForNonexistentProperty(node, type)),
224224
getSuggestionForNonexistentSymbol: (location, name, meaning) => unescapeLeadingUnderscores(getSuggestionForNonexistentSymbol(location, escapeLeadingUnderscores(name), meaning)),
225225
getBaseConstraintOfType,
226-
getJsxNamespace: () => unescapeLeadingUnderscores(getJsxNamespace()),
227-
resolveNameAtLocation(location: Node, name: string, meaning: SymbolFlags): Symbol | undefined {
228-
location = getParseTreeNode(location);
229-
return resolveName(location, escapeLeadingUnderscores(name), meaning, /*nameNotFoundMessage*/ undefined, escapeLeadingUnderscores(name));
226+
resolveName(name, location, meaning) {
227+
return resolveName(location, name as __String, meaning, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined);
230228
},
229+
getJsxNamespace: () => unescapeLeadingUnderscores(getJsxNamespace()),
231230
};
232231

233232
const tupleTypes: GenericType[] = [];

src/compiler/diagnosticMessages.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3673,5 +3673,15 @@
36733673
"Convert function '{0}' to class": {
36743674
"category": "Message",
36753675
"code": 95002
3676+
},
3677+
3678+
"Extract function": {
3679+
"category": "Message",
3680+
"code": 95003
3681+
},
3682+
3683+
"Extract function into '{0}'": {
3684+
"category": "Message",
3685+
"code": 95004
36763686
}
36773687
}

src/compiler/transformers/es2015.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ namespace ts {
394394
function shouldVisitNode(node: Node): boolean {
395395
return (node.transformFlags & TransformFlags.ContainsES2015) !== 0
396396
|| convertedLoopState !== undefined
397-
|| (hierarchyFacts & HierarchyFacts.ConstructorWithCapturedSuper && isStatement(node))
397+
|| (hierarchyFacts & HierarchyFacts.ConstructorWithCapturedSuper && isStatementOrBlock(node))
398398
|| (isIterationStatement(node, /*lookInLabeledStatements*/ false) && shouldConvertIterationStatementBody(node))
399399
|| isTypeScriptClassWrapper(node);
400400
}

src/compiler/types.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2606,9 +2606,8 @@ namespace ts {
26062606
* Does not include properties of primitive types.
26072607
*/
26082608
/* @internal */ getAllPossiblePropertiesOfType(type: Type): Symbol[];
2609-
2609+
/* @internal */ resolveName(name: string, location: Node, meaning: SymbolFlags): Symbol | undefined;
26102610
/* @internal */ getJsxNamespace(): string;
2611-
/* @internal */ resolveNameAtLocation(location: Node, name: string, meaning: SymbolFlags): Symbol | undefined;
26122611
}
26132612

26142613
export enum NodeBuilderFlags {

src/compiler/utilities.ts

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -693,7 +693,7 @@ namespace ts {
693693
// At this point, node is either a qualified name or an identifier
694694
Debug.assert(node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.QualifiedName || node.kind === SyntaxKind.PropertyAccessExpression,
695695
"'node' was expected to be a qualified name, identifier or property access in 'isPartOfTypeNode'.");
696-
// falls through
696+
// falls through
697697
case SyntaxKind.QualifiedName:
698698
case SyntaxKind.PropertyAccessExpression:
699699
case SyntaxKind.ThisKeyword:
@@ -968,7 +968,7 @@ namespace ts {
968968
if (!includeArrowFunctions) {
969969
continue;
970970
}
971-
// falls through
971+
// falls through
972972
case SyntaxKind.FunctionDeclaration:
973973
case SyntaxKind.FunctionExpression:
974974
case SyntaxKind.ModuleDeclaration:
@@ -1027,7 +1027,7 @@ namespace ts {
10271027
if (!stopOnFunctions) {
10281028
continue;
10291029
}
1030-
// falls through
1030+
// falls through
10311031
case SyntaxKind.PropertyDeclaration:
10321032
case SyntaxKind.PropertySignature:
10331033
case SyntaxKind.MethodDeclaration:
@@ -1211,7 +1211,7 @@ namespace ts {
12111211
if (node.parent.kind === SyntaxKind.TypeQuery || isJSXTagName(node)) {
12121212
return true;
12131213
}
1214-
// falls through
1214+
// falls through
12151215
case SyntaxKind.NumericLiteral:
12161216
case SyntaxKind.StringLiteral:
12171217
case SyntaxKind.ThisKeyword:
@@ -1499,8 +1499,8 @@ namespace ts {
14991499
parent.parent.kind === SyntaxKind.VariableStatement;
15001500
const variableStatementNode =
15011501
isInitializerOfVariableDeclarationInStatement ? parent.parent.parent :
1502-
isVariableOfVariableDeclarationStatement ? parent.parent :
1503-
undefined;
1502+
isVariableOfVariableDeclarationStatement ? parent.parent :
1503+
undefined;
15041504
if (variableStatementNode) {
15051505
getJSDocCommentsAndTagsWorker(variableStatementNode);
15061506
}
@@ -1614,7 +1614,7 @@ namespace ts {
16141614
if (isInJavaScriptFile(node)) {
16151615
if (node.type && node.type.kind === SyntaxKind.JSDocVariadicType ||
16161616
forEach(getJSDocParameterTags(node),
1617-
t => t.typeExpression && t.typeExpression.type.kind === SyntaxKind.JSDocVariadicType)) {
1617+
t => t.typeExpression && t.typeExpression.type.kind === SyntaxKind.JSDocVariadicType)) {
16181618
return true;
16191619
}
16201620
}
@@ -1907,7 +1907,7 @@ namespace ts {
19071907
if (node.asteriskToken) {
19081908
flags |= FunctionFlags.Generator;
19091909
}
1910-
// falls through
1910+
// falls through
19111911
case SyntaxKind.ArrowFunction:
19121912
if (hasModifier(node, ModifierFlags.Async)) {
19131913
flags |= FunctionFlags.Async;
@@ -5123,6 +5123,19 @@ namespace ts {
51235123
return isUnaryExpressionKind(skipPartiallyEmittedExpressions(node).kind);
51245124
}
51255125

5126+
/* @internal */
5127+
export function isUnaryExpressionWithWrite(expr: Node): expr is PrefixUnaryExpression | PostfixUnaryExpression {
5128+
switch (expr.kind) {
5129+
case SyntaxKind.PostfixUnaryExpression:
5130+
return true;
5131+
case SyntaxKind.PrefixUnaryExpression:
5132+
return (<PrefixUnaryExpression>expr).operator === SyntaxKind.PlusPlusToken ||
5133+
(<PrefixUnaryExpression>expr).operator === SyntaxKind.MinusMinusToken;
5134+
default:
5135+
return false;
5136+
}
5137+
}
5138+
51265139
function isExpressionKind(kind: SyntaxKind) {
51275140
return kind === SyntaxKind.ConditionalExpression
51285141
|| kind === SyntaxKind.YieldExpression
@@ -5337,7 +5350,25 @@ namespace ts {
53375350
const kind = node.kind;
53385351
return isStatementKindButNotDeclarationKind(kind)
53395352
|| isDeclarationStatementKind(kind)
5340-
|| kind === SyntaxKind.Block;
5353+
|| isBlockStatement(node);
5354+
}
5355+
5356+
/* @internal */
5357+
export function isStatementOrBlock(node: Node): node is Statement {
5358+
const kind = node.kind;
5359+
return isStatementKindButNotDeclarationKind(kind)
5360+
|| isDeclarationStatementKind(kind)
5361+
|| node.kind === SyntaxKind.Block;
5362+
}
5363+
5364+
function isBlockStatement(node: Node): node is Block {
5365+
if (node.kind !== SyntaxKind.Block) return false;
5366+
if (node.parent !== undefined) {
5367+
if (node.parent.kind === SyntaxKind.TryStatement || node.parent.kind === SyntaxKind.CatchClause) {
5368+
return false;
5369+
}
5370+
}
5371+
return !isFunctionBlock(node);
53415372
}
53425373

53435374
// Module references

src/harness/fourslash.ts

Lines changed: 79 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,9 @@ namespace FourSlash {
187187

188188
// The current caret position in the active file
189189
public currentCaretPosition = 0;
190+
// The position of the end of the current selection, or -1 if nothing is selected
191+
public selectionEnd = -1;
192+
190193
public lastKnownMarker = "";
191194

192195
// The file that's currently 'opened'
@@ -433,11 +436,19 @@ namespace FourSlash {
433436

434437
public goToPosition(pos: number) {
435438
this.currentCaretPosition = pos;
439+
this.selectionEnd = -1;
440+
}
441+
442+
public select(startMarker: string, endMarker: string) {
443+
const start = this.getMarkerByName(startMarker), end = this.getMarkerByName(endMarker);
444+
this.goToPosition(start.position);
445+
this.selectionEnd = end.position;
436446
}
437447

438448
public moveCaretRight(count = 1) {
439449
this.currentCaretPosition += count;
440450
this.currentCaretPosition = Math.min(this.currentCaretPosition, this.getFileContent(this.activeFile.fileName).length);
451+
this.selectionEnd = -1;
441452
}
442453

443454
// Opens a file given its 0-based index or fileName
@@ -980,9 +991,9 @@ namespace FourSlash {
980991
}
981992

982993
for (const reference of expectedReferences) {
983-
const {fileName, start, end} = reference;
994+
const { fileName, start, end } = reference;
984995
if (reference.marker && reference.marker.data) {
985-
const {isWriteAccess, isDefinition} = reference.marker.data;
996+
const { isWriteAccess, isDefinition } = reference.marker.data;
986997
this.verifyReferencesWorker(actualReferences, fileName, start, end, isWriteAccess, isDefinition);
987998
}
988999
else {
@@ -1193,7 +1204,7 @@ namespace FourSlash {
11931204
displayParts: ts.SymbolDisplayPart[],
11941205
documentation: ts.SymbolDisplayPart[],
11951206
tags: ts.JSDocTagInfo[]
1196-
) {
1207+
) {
11971208

11981209
const actualQuickInfo = this.languageService.getQuickInfoAtPosition(this.activeFile.fileName, this.currentCaretPosition);
11991210
assert.equal(actualQuickInfo.kind, kind, this.messageAtLastKnownMarker("QuickInfo kind"));
@@ -1789,19 +1800,16 @@ namespace FourSlash {
17891800
// We get back a set of edits, but langSvc.editScript only accepts one at a time. Use this to keep track
17901801
// of the incremental offset from each edit to the next. We assume these edit ranges don't overlap
17911802

1792-
edits = edits.sort((a, b) => a.span.start - b.span.start);
1793-
for (let i = 0; i < edits.length - 1; i++) {
1794-
const firstEditSpan = edits[i].span;
1795-
const firstEditEnd = firstEditSpan.start + firstEditSpan.length;
1796-
assert.isTrue(firstEditEnd <= edits[i + 1].span.start);
1797-
}
1803+
// Copy this so we don't ruin someone else's copy
1804+
edits = JSON.parse(JSON.stringify(edits));
17981805

17991806
// Get a snapshot of the content of the file so we can make sure any formatting edits didn't destroy non-whitespace characters
18001807
const oldContent = this.getFileContent(fileName);
18011808
let runningOffset = 0;
18021809

1803-
for (const edit of edits) {
1804-
const offsetStart = edit.span.start + runningOffset;
1810+
for (let i = 0; i < edits.length; i++) {
1811+
const edit = edits[i];
1812+
const offsetStart = edit.span.start;
18051813
const offsetEnd = offsetStart + edit.span.length;
18061814
this.editScriptAndUpdateMarkers(fileName, offsetStart, offsetEnd, edit.newText);
18071815
const editDelta = edit.newText.length - edit.span.length;
@@ -1816,8 +1824,13 @@ namespace FourSlash {
18161824
}
18171825
}
18181826
runningOffset += editDelta;
1819-
// TODO: Consider doing this at least some of the time for higher fidelity. Currently causes a failure (bug 707150)
1820-
// this.languageService.getScriptLexicalStructure(fileName);
1827+
1828+
// Update positions of any future edits affected by this change
1829+
for (let j = i + 1; j < edits.length; j++) {
1830+
if (edits[j].span.start >= edits[i].span.start) {
1831+
edits[j].span.start += editDelta;
1832+
}
1833+
}
18211834
}
18221835

18231836
if (isFormattingEdit) {
@@ -1901,7 +1914,7 @@ namespace FourSlash {
19011914
this.goToPosition(len);
19021915
}
19031916

1904-
public goToRangeStart({fileName, start}: Range) {
1917+
public goToRangeStart({ fileName, start }: Range) {
19051918
this.openFile(fileName);
19061919
this.goToPosition(start);
19071920
}
@@ -2075,7 +2088,7 @@ namespace FourSlash {
20752088
return result;
20762089
}
20772090

2078-
private rangeText({fileName, start, end}: Range): string {
2091+
private rangeText({ fileName, start, end }: Range): string {
20792092
return this.getFileContent(fileName).slice(start, end);
20802093
}
20812094

@@ -2361,7 +2374,7 @@ namespace FourSlash {
23612374
private applyCodeActions(actions: ts.CodeAction[], index?: number): void {
23622375
if (index === undefined) {
23632376
if (!(actions && actions.length === 1)) {
2364-
this.raiseError(`Should find exactly one codefix, but ${actions ? actions.length : "none"} found. ${actions ? actions.map(a => `${Harness.IO.newLine()} "${a.description}"`) : "" }`);
2377+
this.raiseError(`Should find exactly one codefix, but ${actions ? actions.length : "none"} found. ${actions ? actions.map(a => `${Harness.IO.newLine()} "${a.description}"`) : ""}`);
23652378
}
23662379
index = 0;
23672380
}
@@ -2736,6 +2749,30 @@ namespace FourSlash {
27362749
}
27372750
}
27382751

2752+
private getSelection() {
2753+
return ({
2754+
pos: this.currentCaretPosition,
2755+
end: this.selectionEnd === -1 ? this.currentCaretPosition : this.selectionEnd
2756+
});
2757+
}
2758+
2759+
public verifyRefactorAvailable(negative: boolean, name?: string, subName?: string) {
2760+
const selection = this.getSelection();
2761+
2762+
let refactors = this.languageService.getApplicableRefactors(this.activeFile.fileName, selection) || [];
2763+
if (name) {
2764+
refactors = refactors.filter(r => r.name === name && (subName === undefined || r.actions.some(a => a.name === subName)));
2765+
}
2766+
const isAvailable = refactors.length > 0;
2767+
2768+
if (negative && isAvailable) {
2769+
this.raiseError(`verifyApplicableRefactorAvailableForRange failed - expected no refactor but found some.`);
2770+
}
2771+
else if (!negative && !isAvailable) {
2772+
this.raiseError(`verifyApplicableRefactorAvailableForRange failed - expected a refactor but found none.`);
2773+
}
2774+
}
2775+
27392776
public verifyApplicableRefactorAvailableForRange(negative: boolean) {
27402777
const ranges = this.getRanges();
27412778
if (!(ranges && ranges.length === 1)) {
@@ -2752,6 +2789,20 @@ namespace FourSlash {
27522789
}
27532790
}
27542791

2792+
public applyRefactor(refactorName: string, actionName: string) {
2793+
const range = this.getSelection();
2794+
const refactors = this.languageService.getApplicableRefactors(this.activeFile.fileName, range);
2795+
const refactor = ts.find(refactors, r => r.name === refactorName);
2796+
if (!refactor) {
2797+
this.raiseError(`The expected refactor: ${refactorName} is not available at the marker location.`);
2798+
}
2799+
2800+
const editInfo = this.languageService.getEditsForRefactor(this.activeFile.fileName, this.formatCodeSettings, range, refactorName, actionName);
2801+
for (const edit of editInfo.edits) {
2802+
this.applyEdits(edit.fileName, edit.textChanges, /*isFormattingEdit*/ false);
2803+
}
2804+
}
2805+
27552806
public verifyFileAfterApplyingRefactorAtMarker(
27562807
markerName: string,
27572808
expectedContent: string,
@@ -3496,6 +3547,10 @@ namespace FourSlashInterface {
34963547
public file(indexOrName: any, content?: string, scriptKindName?: string): void {
34973548
this.state.openFile(indexOrName, content, scriptKindName);
34983549
}
3550+
3551+
public select(startMarker: string, endMarker: string) {
3552+
this.state.select(startMarker, endMarker);
3553+
}
34993554
}
35003555

35013556
export class VerifyNegatable {
@@ -3617,6 +3672,10 @@ namespace FourSlashInterface {
36173672
public applicableRefactorAvailableForRange() {
36183673
this.state.verifyApplicableRefactorAvailableForRange(this.negative);
36193674
}
3675+
3676+
public refactorAvailable(name?: string, subName?: string) {
3677+
this.state.verifyRefactorAvailable(this.negative, name, subName);
3678+
}
36203679
}
36213680

36223681
export class Verify extends VerifyNegatable {
@@ -4012,6 +4071,10 @@ namespace FourSlashInterface {
40124071
public disableFormatting() {
40134072
this.state.enableFormatting = false;
40144073
}
4074+
4075+
public applyRefactor(refactorName: string, actionName: string) {
4076+
this.state.applyRefactor(refactorName, actionName);
4077+
}
40154078
}
40164079

40174080
export class Debug {

0 commit comments

Comments
 (0)