Skip to content

Commit c110ecb

Browse files
Merge pull request #17625 from RyanCavanaugh/extract-method-2
Extract Method
2 parents f64b8ad + 8a92315 commit c110ecb

Some content is hidden

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

64 files changed

+4358
-76
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, escapeLeadingUnderscores(name), 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
@@ -3681,5 +3681,15 @@
36813681
"Convert function '{0}' to class": {
36823682
"category": "Message",
36833683
"code": 95002
3684+
},
3685+
3686+
"Extract function": {
3687+
"category": "Message",
3688+
"code": 95003
3689+
},
3690+
3691+
"Extract function into '{0}'": {
3692+
"category": "Message",
3693+
"code": 95004
36843694
}
36853695
}

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 && (isStatement(node) || (node.kind === SyntaxKind.Block)))
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
@@ -2631,9 +2631,8 @@ namespace ts {
26312631
* Does not include properties of primitive types.
26322632
*/
26332633
/* @internal */ getAllPossiblePropertiesOfType(type: Type): Symbol[];
2634-
2634+
/* @internal */ resolveName(name: string, location: Node, meaning: SymbolFlags): Symbol | undefined;
26352635
/* @internal */ getJsxNamespace(): string;
2636-
/* @internal */ resolveNameAtLocation(location: Node, name: string, meaning: SymbolFlags): Symbol | undefined;
26372636
}
26382637

26392638
export enum NodeBuilderFlags {

src/compiler/utilities.ts

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,8 @@ namespace ts {
443443
return isExternalModule(node) || compilerOptions.isolatedModules;
444444
}
445445

446-
function isBlockScope(node: Node, parentNode: Node) {
446+
/* @internal */
447+
export function isBlockScope(node: Node, parentNode: Node) {
447448
switch (node.kind) {
448449
case SyntaxKind.SourceFile:
449450
case SyntaxKind.CaseBlock:
@@ -1502,8 +1503,8 @@ namespace ts {
15021503
parent.parent.kind === SyntaxKind.VariableStatement;
15031504
const variableStatementNode =
15041505
isInitializerOfVariableDeclarationInStatement ? parent.parent.parent :
1505-
isVariableOfVariableDeclarationStatement ? parent.parent :
1506-
undefined;
1506+
isVariableOfVariableDeclarationStatement ? parent.parent :
1507+
undefined;
15071508
if (variableStatementNode) {
15081509
getJSDocCommentsAndTagsWorker(variableStatementNode);
15091510
}
@@ -1617,7 +1618,7 @@ namespace ts {
16171618
if (isInJavaScriptFile(node)) {
16181619
if (node.type && node.type.kind === SyntaxKind.JSDocVariadicType ||
16191620
forEach(getJSDocParameterTags(node),
1620-
t => t.typeExpression && t.typeExpression.type.kind === SyntaxKind.JSDocVariadicType)) {
1621+
t => t.typeExpression && t.typeExpression.type.kind === SyntaxKind.JSDocVariadicType)) {
16211622
return true;
16221623
}
16231624
}
@@ -4983,6 +4984,19 @@ namespace ts {
49834984
return isUnaryExpressionKind(skipPartiallyEmittedExpressions(node).kind);
49844985
}
49854986

4987+
/* @internal */
4988+
export function isUnaryExpressionWithWrite(expr: Node): expr is PrefixUnaryExpression | PostfixUnaryExpression {
4989+
switch (expr.kind) {
4990+
case SyntaxKind.PostfixUnaryExpression:
4991+
return true;
4992+
case SyntaxKind.PrefixUnaryExpression:
4993+
return (<PrefixUnaryExpression>expr).operator === SyntaxKind.PlusPlusToken ||
4994+
(<PrefixUnaryExpression>expr).operator === SyntaxKind.MinusMinusToken;
4995+
default:
4996+
return false;
4997+
}
4998+
}
4999+
49865000
function isExpressionKind(kind: SyntaxKind) {
49875001
return kind === SyntaxKind.ConditionalExpression
49885002
|| kind === SyntaxKind.YieldExpression
@@ -5197,7 +5211,17 @@ namespace ts {
51975211
const kind = node.kind;
51985212
return isStatementKindButNotDeclarationKind(kind)
51995213
|| isDeclarationStatementKind(kind)
5200-
|| kind === SyntaxKind.Block;
5214+
|| isBlockStatement(node);
5215+
}
5216+
5217+
function isBlockStatement(node: Node): node is Block {
5218+
if (node.kind !== SyntaxKind.Block) return false;
5219+
if (node.parent !== undefined) {
5220+
if (node.parent.kind === SyntaxKind.TryStatement || node.parent.kind === SyntaxKind.CatchClause) {
5221+
return false;
5222+
}
5223+
}
5224+
return !isFunctionBlock(node);
52015225
}
52025226

52035227
// 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
@@ -967,9 +978,9 @@ namespace FourSlash {
967978
}
968979

969980
for (const reference of expectedReferences) {
970-
const {fileName, start, end} = reference;
981+
const { fileName, start, end } = reference;
971982
if (reference.marker && reference.marker.data) {
972-
const {isWriteAccess, isDefinition} = reference.marker.data;
983+
const { isWriteAccess, isDefinition } = reference.marker.data;
973984
this.verifyReferencesWorker(actualReferences, fileName, start, end, isWriteAccess, isDefinition);
974985
}
975986
else {
@@ -1180,7 +1191,7 @@ namespace FourSlash {
11801191
displayParts: ts.SymbolDisplayPart[],
11811192
documentation: ts.SymbolDisplayPart[],
11821193
tags: ts.JSDocTagInfo[]
1183-
) {
1194+
) {
11841195

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

1779-
edits = edits.sort((a, b) => a.span.start - b.span.start);
1780-
for (let i = 0; i < edits.length - 1; i++) {
1781-
const firstEditSpan = edits[i].span;
1782-
const firstEditEnd = firstEditSpan.start + firstEditSpan.length;
1783-
assert.isTrue(firstEditEnd <= edits[i + 1].span.start);
1784-
}
1790+
// Copy this so we don't ruin someone else's copy
1791+
edits = JSON.parse(JSON.stringify(edits));
17851792

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

1790-
for (const edit of edits) {
1791-
const offsetStart = edit.span.start + runningOffset;
1797+
for (let i = 0; i < edits.length; i++) {
1798+
const edit = edits[i];
1799+
const offsetStart = edit.span.start;
17921800
const offsetEnd = offsetStart + edit.span.length;
17931801
this.editScriptAndUpdateMarkers(fileName, offsetStart, offsetEnd, edit.newText);
17941802
const editDelta = edit.newText.length - edit.span.length;
@@ -1803,8 +1811,13 @@ namespace FourSlash {
18031811
}
18041812
}
18051813
runningOffset += editDelta;
1806-
// TODO: Consider doing this at least some of the time for higher fidelity. Currently causes a failure (bug 707150)
1807-
// this.languageService.getScriptLexicalStructure(fileName);
1814+
1815+
// Update positions of any future edits affected by this change
1816+
for (let j = i + 1; j < edits.length; j++) {
1817+
if (edits[j].span.start >= edits[i].span.start) {
1818+
edits[j].span.start += editDelta;
1819+
}
1820+
}
18081821
}
18091822

18101823
if (isFormattingEdit) {
@@ -1888,7 +1901,7 @@ namespace FourSlash {
18881901
this.goToPosition(len);
18891902
}
18901903

1891-
public goToRangeStart({fileName, start}: Range) {
1904+
public goToRangeStart({ fileName, start }: Range) {
18921905
this.openFile(fileName);
18931906
this.goToPosition(start);
18941907
}
@@ -2062,7 +2075,7 @@ namespace FourSlash {
20622075
return result;
20632076
}
20642077

2065-
private rangeText({fileName, start, end}: Range): string {
2078+
private rangeText({ fileName, start, end }: Range): string {
20662079
return this.getFileContent(fileName).slice(start, end);
20672080
}
20682081

@@ -2348,7 +2361,7 @@ namespace FourSlash {
23482361
private applyCodeActions(actions: ts.CodeAction[], index?: number): void {
23492362
if (index === undefined) {
23502363
if (!(actions && actions.length === 1)) {
2351-
this.raiseError(`Should find exactly one codefix, but ${actions ? actions.length : "none"} found. ${actions ? actions.map(a => `${Harness.IO.newLine()} "${a.description}"`) : "" }`);
2364+
this.raiseError(`Should find exactly one codefix, but ${actions ? actions.length : "none"} found. ${actions ? actions.map(a => `${Harness.IO.newLine()} "${a.description}"`) : ""}`);
23522365
}
23532366
index = 0;
23542367
}
@@ -2723,6 +2736,30 @@ namespace FourSlash {
27232736
}
27242737
}
27252738

2739+
private getSelection() {
2740+
return ({
2741+
pos: this.currentCaretPosition,
2742+
end: this.selectionEnd === -1 ? this.currentCaretPosition : this.selectionEnd
2743+
});
2744+
}
2745+
2746+
public verifyRefactorAvailable(negative: boolean, name?: string, subName?: string) {
2747+
const selection = this.getSelection();
2748+
2749+
let refactors = this.languageService.getApplicableRefactors(this.activeFile.fileName, selection) || [];
2750+
if (name) {
2751+
refactors = refactors.filter(r => r.name === name && (subName === undefined || r.actions.some(a => a.name === subName)));
2752+
}
2753+
const isAvailable = refactors.length > 0;
2754+
2755+
if (negative && isAvailable) {
2756+
this.raiseError(`verifyApplicableRefactorAvailableForRange failed - expected no refactor but found some: ${refactors.map(r => r.name).join(", ")}`);
2757+
}
2758+
else if (!negative && !isAvailable) {
2759+
this.raiseError(`verifyApplicableRefactorAvailableForRange failed - expected a refactor but found none.`);
2760+
}
2761+
}
2762+
27262763
public verifyApplicableRefactorAvailableForRange(negative: boolean) {
27272764
const ranges = this.getRanges();
27282765
if (!(ranges && ranges.length === 1)) {
@@ -2739,6 +2776,20 @@ namespace FourSlash {
27392776
}
27402777
}
27412778

2779+
public applyRefactor(refactorName: string, actionName: string) {
2780+
const range = this.getSelection();
2781+
const refactors = this.languageService.getApplicableRefactors(this.activeFile.fileName, range);
2782+
const refactor = ts.find(refactors, r => r.name === refactorName);
2783+
if (!refactor) {
2784+
this.raiseError(`The expected refactor: ${refactorName} is not available at the marker location.`);
2785+
}
2786+
2787+
const editInfo = this.languageService.getEditsForRefactor(this.activeFile.fileName, this.formatCodeSettings, range, refactorName, actionName);
2788+
for (const edit of editInfo.edits) {
2789+
this.applyEdits(edit.fileName, edit.textChanges, /*isFormattingEdit*/ false);
2790+
}
2791+
}
2792+
27422793
public verifyFileAfterApplyingRefactorAtMarker(
27432794
markerName: string,
27442795
expectedContent: string,
@@ -3483,6 +3534,10 @@ namespace FourSlashInterface {
34833534
public file(indexOrName: any, content?: string, scriptKindName?: string): void {
34843535
this.state.openFile(indexOrName, content, scriptKindName);
34853536
}
3537+
3538+
public select(startMarker: string, endMarker: string) {
3539+
this.state.select(startMarker, endMarker);
3540+
}
34863541
}
34873542

34883543
export class VerifyNegatable {
@@ -3604,6 +3659,10 @@ namespace FourSlashInterface {
36043659
public applicableRefactorAvailableForRange() {
36053660
this.state.verifyApplicableRefactorAvailableForRange(this.negative);
36063661
}
3662+
3663+
public refactorAvailable(name?: string, subName?: string) {
3664+
this.state.verifyRefactorAvailable(this.negative, name, subName);
3665+
}
36073666
}
36083667

36093668
export class Verify extends VerifyNegatable {
@@ -3999,6 +4058,10 @@ namespace FourSlashInterface {
39994058
public disableFormatting() {
40004059
this.state.enableFormatting = false;
40014060
}
4061+
4062+
public applyRefactor(refactorName: string, actionName: string) {
4063+
this.state.applyRefactor(refactorName, actionName);
4064+
}
40024065
}
40034066

40044067
export class Debug {

0 commit comments

Comments
 (0)