diff --git a/lib/tsc.js b/lib/tsc.js index 33eeb6de3af..d0fb24e1321 100644 --- a/lib/tsc.js +++ b/lib/tsc.js @@ -11088,7 +11088,8 @@ var ts; return token() === 24 || isIdentifierOrPattern() || ts.isModifierKind(token()) || - token() === 57 || isStartOfType(); + token() === 57 || + isStartOfType(true); } function parseParameter() { var node = createNode(146); @@ -11427,7 +11428,7 @@ var ts; return parseTypeReference(); } } - function isStartOfType() { + function isStartOfType(inStartOfParameter) { switch (token()) { case 119: case 136: @@ -11454,9 +11455,9 @@ var ts; case 39: return true; case 38: - return lookAhead(nextTokenIsNumericLiteral); + return !inStartOfParameter && lookAhead(nextTokenIsNumericLiteral); case 19: - return lookAhead(isStartOfParenthesizedOrFunctionType); + return !inStartOfParameter && lookAhead(isStartOfParenthesizedOrFunctionType); default: return isIdentifier(); } diff --git a/lib/tsserver.js b/lib/tsserver.js index 21b0b83c062..c1075eb4971 100644 --- a/lib/tsserver.js +++ b/lib/tsserver.js @@ -12958,7 +12958,8 @@ var ts; return token() === 24 || isIdentifierOrPattern() || ts.isModifierKind(token()) || - token() === 57 || isStartOfType(); + token() === 57 || + isStartOfType(true); } function parseParameter() { var node = createNode(146); @@ -13297,7 +13298,7 @@ var ts; return parseTypeReference(); } } - function isStartOfType() { + function isStartOfType(inStartOfParameter) { switch (token()) { case 119: case 136: @@ -13324,9 +13325,9 @@ var ts; case 39: return true; case 38: - return lookAhead(nextTokenIsNumericLiteral); + return !inStartOfParameter && lookAhead(nextTokenIsNumericLiteral); case 19: - return lookAhead(isStartOfParenthesizedOrFunctionType); + return !inStartOfParameter && lookAhead(isStartOfParenthesizedOrFunctionType); default: return isIdentifier(); } @@ -74425,7 +74426,9 @@ var ts; deleteCallback(); } return { - edits: changeTracker.getChanges() + edits: changeTracker.getChanges(), + renameFilename: undefined, + renameLocation: undefined, }; function deleteNode(node, inList) { if (inList === void 0) { inList = false; } @@ -74576,11 +74579,11 @@ var ts; var usedNames = ts.createMap(); var i = 0; for (var _i = 0, extractions_1 = extractions; _i < extractions_1.length; _i++) { - var extr = extractions_1[_i]; - if (extr.errors && extr.errors.length) { + var _a = extractions_1[_i], scopeDescription = _a.scopeDescription, errors = _a.errors; + if (errors.length) { continue; } - var description = ts.formatStringFromArgs(ts.Diagnostics.Extract_function_into_0.message, [extr.scopeDescription]); + var description = ts.formatStringFromArgs(ts.Diagnostics.Extract_function_into_0.message, [scopeDescription]); if (!usedNames.has(description)) { usedNames.set(description, true); actions.push({ @@ -74608,9 +74611,7 @@ var ts; ts.Debug.assert(!!parsedIndexMatch, "Scope name should have matched the regexp"); var index = +parsedIndexMatch[1]; ts.Debug.assert(isFinite(index), "Expected to parse a finite number from the scope index"); - var extractions = getPossibleExtractions(targetRange, context, index); - ts.Debug.assert(extractions !== undefined, "The extraction went missing? How?"); - return ({ edits: extractions[0].changes }); + return getExtractionAtIndex(targetRange, context, index); } var Messages; (function (Messages) { @@ -74639,7 +74640,7 @@ var ts; RangeFacts[RangeFacts["IsAsyncFunction"] = 4] = "IsAsyncFunction"; RangeFacts[RangeFacts["UsesThis"] = 8] = "UsesThis"; RangeFacts[RangeFacts["InStaticRegion"] = 16] = "InStaticRegion"; - })(RangeFacts = extractMethod_1.RangeFacts || (extractMethod_1.RangeFacts = {})); + })(RangeFacts || (RangeFacts = {})); function getRangeToExtract(sourceFile, span) { var length = span.length || 0; var start = getParentNodeInSpan(ts.getTokenAtPosition(sourceFile, span.start, false), sourceFile, span); @@ -74879,7 +74880,7 @@ var ts; return undefined; } function isValidExtractionTarget(node) { - return (node.kind === 228) || ts.isSourceFile(node) || isModuleBlock(node) || ts.isClassLike(node); + return (node.kind === 228) || ts.isSourceFile(node) || ts.isModuleBlock(node) || ts.isClassLike(node); } function collectEnclosingScopes(range) { var current = isReadonlyArray(range.range) ? ts.firstOrUndefined(range.range) : range.range; @@ -74904,9 +74905,21 @@ var ts; } return scopes; } - extractMethod_1.collectEnclosingScopes = collectEnclosingScopes; - function getPossibleExtractions(targetRange, context, requestedChangesIndex) { - if (requestedChangesIndex === void 0) { requestedChangesIndex = undefined; } + function getExtractionAtIndex(targetRange, context, requestedChangesIndex) { + var _a = getPossibleExtractionsWorker(targetRange, context), scopes = _a.scopes, _b = _a.readsAndWrites, target = _b.target, usagesPerScope = _b.usagesPerScope, errorsPerScope = _b.errorsPerScope; + ts.Debug.assert(!errorsPerScope[requestedChangesIndex].length, "The extraction went missing? How?"); + context.cancellationToken.throwIfCancellationRequested(); + return extractFunctionInScope(target, scopes[requestedChangesIndex], usagesPerScope[requestedChangesIndex], targetRange, context); + } + extractMethod_1.getExtractionAtIndex = getExtractionAtIndex; + function getPossibleExtractions(targetRange, context) { + var extractions = getPossibleExtractionsWorker(targetRange, context); + return extractions && extractions.scopes.map(function (scope, i) { + return ({ scopeDescription: getDescriptionForScope(scope), errors: extractions.readsAndWrites.errorsPerScope[i] }); + }); + } + extractMethod_1.getPossibleExtractions = getPossibleExtractions; + function getPossibleExtractionsWorker(targetRange, context) { var sourceFile = context.file; if (targetRange === undefined) { return undefined; @@ -74916,29 +74929,9 @@ var ts; return undefined; } var enclosingTextRange = getEnclosingTextRange(targetRange, sourceFile); - var _a = collectReadsAndWrites(targetRange, scopes, enclosingTextRange, sourceFile, context.program.getTypeChecker()), target = _a.target, usagesPerScope = _a.usagesPerScope, errorsPerScope = _a.errorsPerScope; - context.cancellationToken.throwIfCancellationRequested(); - if (requestedChangesIndex !== undefined) { - if (errorsPerScope[requestedChangesIndex].length) { - return undefined; - } - return [extractFunctionInScope(target, scopes[requestedChangesIndex], usagesPerScope[requestedChangesIndex], targetRange, context)]; - } - else { - return scopes.map(function (scope, i) { - var errors = errorsPerScope[i]; - if (errors.length) { - return { - scope: scope, - scopeDescription: getDescriptionForScope(scope), - errors: errors - }; - } - return { scope: scope, scopeDescription: getDescriptionForScope(scope) }; - }); - } + var readsAndWrites = collectReadsAndWrites(targetRange, scopes, enclosingTextRange, sourceFile, context.program.getTypeChecker()); + return { scopes: scopes, readsAndWrites: readsAndWrites }; } - extractMethod_1.getPossibleExtractions = getPossibleExtractions; function getDescriptionForScope(scope) { if (ts.isFunctionLike(scope)) { switch (scope.kind) { @@ -74960,7 +74953,7 @@ var ts; return "'set " + scope.name.getText() + "'"; } } - else if (isModuleBlock(scope)) { + else if (ts.isModuleBlock(scope)) { return "namespace '" + scope.parent.name.getText() + "'"; } else if (ts.isClassLike(scope)) { @@ -74977,14 +74970,10 @@ var ts; return "unknown"; } } - function getUniqueName(isNameOkay) { + function getUniqueName(fileText) { var functionNameText = "newFunction"; - if (isNameOkay(functionNameText)) { - return functionNameText; - } - var i = 1; - while (!isNameOkay(functionNameText = "newFunction_" + i)) { - i++; + for (var i = 1; fileText.indexOf(functionNameText) !== -1; i++) { + functionNameText = "newFunction_" + i; } return functionNameText; } @@ -74992,10 +74981,9 @@ var ts; var usagesInScope = _a.usages, substitutions = _a.substitutions; var checker = context.program.getTypeChecker(); var file = scope.getSourceFile(); - var functionNameText = getUniqueName(function (n) { return !file.identifiers.has(n); }); + var functionNameText = getUniqueName(file.text); var isJS = ts.isInJavaScriptFile(scope); var functionName = ts.createIdentifier(functionNameText); - var functionReference = ts.createIdentifier(functionNameText); var returnType = undefined; var parameters = []; var callArguments = []; @@ -75018,7 +75006,7 @@ var ts; var contextualType = checker.getContextualType(node); returnType = checker.typeToTypeNode(contextualType); } - var _b = transformFunctionBody(node), body = _b.body, returnValueProperty = _b.returnValueProperty; + var _b = transformFunctionBody(node, writes, substitutions, !!(range.facts & RangeFacts.HasReturn)), body = _b.body, returnValueProperty = _b.returnValueProperty; var newFunction; if (ts.isClassLike(scope)) { var modifiers = isJS ? [] : [ts.createToken(112)]; @@ -75036,7 +75024,8 @@ var ts; var changeTracker = ts.textChanges.ChangeTracker.fromCodeFixContext(context); changeTracker.insertNodeBefore(context.file, scope.getLastToken(), newFunction, { prefix: context.newLineCharacter, suffix: context.newLineCharacter }); var newNodes = []; - var call = ts.createCall(ts.isClassLike(scope) ? ts.createPropertyAccess(range.facts & RangeFacts.InStaticRegion ? ts.createIdentifier(scope.name.getText()) : ts.createThis(), functionReference) : functionReference, undefined, callArguments); + var called = getCalledExpression(scope, range, functionNameText); + var call = ts.createCall(called, undefined, callArguments); if (range.facts & RangeFacts.IsGenerator) { call = ts.createYield(ts.createToken(39), call); } @@ -75086,65 +75075,85 @@ var ts; else { changeTracker.replaceNodeWithNodes(context.file, range.range, newNodes, { nodeSeparator: context.newLineCharacter }); } - return { - scope: scope, - scopeDescription: getDescriptionForScope(scope), - changes: changeTracker.getChanges() - }; - function getPropertyAssignmentsForWrites(writes) { - return writes.map(function (w) { return ts.createShorthandPropertyAssignment(w.symbol.name); }); - } - function generateReturnValueProperty() { - return "__return"; - } - function transformFunctionBody(body) { - if (ts.isBlock(body) && !writes && substitutions.size === 0) { - return { body: ts.createBlock(body.statements, true), returnValueProperty: undefined }; - } - var returnValueProperty; - var statements = ts.createNodeArray(ts.isBlock(body) ? body.statements.slice(0) : [ts.isStatement(body) ? body : ts.createReturn(body)]); - if (writes || substitutions.size) { - var rewrittenStatements = ts.visitNodes(statements, visitor).slice(); - if (writes && !(range.facts & RangeFacts.HasReturn) && ts.isStatement(body)) { - var assignments = getPropertyAssignmentsForWrites(writes); - if (assignments.length === 1) { - rewrittenStatements.push(ts.createReturn(assignments[0].name)); - } - else { - rewrittenStatements.push(ts.createReturn(ts.createObjectLiteral(assignments))); - } + var edits = changeTracker.getChanges(); + var renameRange = isReadonlyArray(range.range) ? range.range[0] : range.range; + var renameFilename = renameRange.getSourceFile().fileName; + var renameLocation = getRenameLocation(edits, renameFilename, functionNameText); + return { renameFilename: renameFilename, renameLocation: renameLocation, edits: edits }; + } + function getRenameLocation(edits, renameFilename, functionNameText) { + var delta = 0; + for (var _i = 0, edits_1 = edits; _i < edits_1.length; _i++) { + var _a = edits_1[_i], fileName = _a.fileName, textChanges_1 = _a.textChanges; + ts.Debug.assert(fileName === renameFilename); + for (var _b = 0, textChanges_2 = textChanges_1; _b < textChanges_2.length; _b++) { + var change = textChanges_2[_b]; + var span_17 = change.span, newText = change.newText; + var index = newText.indexOf(functionNameText); + if (index !== -1) { + return span_17.start + delta + index; } - return { body: ts.createBlock(rewrittenStatements, true), returnValueProperty: returnValueProperty }; + delta += newText.length - span_17.length; } - else { - return { body: ts.createBlock(statements, true), returnValueProperty: undefined }; - } - function visitor(node) { - if (node.kind === 219 && writes) { - var assignments = getPropertyAssignmentsForWrites(writes); - if (node.expression) { - if (!returnValueProperty) { - returnValueProperty = generateReturnValueProperty(); - } - assignments.unshift(ts.createPropertyAssignment(returnValueProperty, ts.visitNode(node.expression, visitor))); - } - if (assignments.length === 1) { - return ts.createReturn(assignments[0].name); - } - else { - return ts.createReturn(ts.createObjectLiteral(assignments)); - } + } + throw new Error(); + } + function getCalledExpression(scope, range, functionNameText) { + var functionReference = ts.createIdentifier(functionNameText); + if (ts.isClassLike(scope)) { + var lhs = range.facts & RangeFacts.InStaticRegion ? ts.createIdentifier(scope.name.text) : ts.createThis(); + return ts.createPropertyAccess(lhs, functionReference); + } + else { + return functionReference; + } + } + function transformFunctionBody(body, writes, substitutions, hasReturn) { + if (ts.isBlock(body) && !writes && substitutions.size === 0) { + return { body: ts.createBlock(body.statements, true), returnValueProperty: undefined }; + } + var returnValueProperty; + var statements = ts.createNodeArray(ts.isBlock(body) ? body.statements.slice(0) : [ts.isStatement(body) ? body : ts.createReturn(body)]); + if (writes || substitutions.size) { + var rewrittenStatements = ts.visitNodes(statements, visitor).slice(); + if (writes && !hasReturn && ts.isStatement(body)) { + var assignments = getPropertyAssignmentsForWrites(writes); + if (assignments.length === 1) { + rewrittenStatements.push(ts.createReturn(assignments[0].name)); } else { - var substitution = substitutions.get(ts.getNodeId(node).toString()); - return substitution || ts.visitEachChild(node, visitor, ts.nullTransformationContext); + rewrittenStatements.push(ts.createReturn(ts.createObjectLiteral(assignments))); } } + return { body: ts.createBlock(rewrittenStatements, true), returnValueProperty: returnValueProperty }; + } + else { + return { body: ts.createBlock(statements, true), returnValueProperty: undefined }; + } + function visitor(node) { + if (node.kind === 219 && writes) { + var assignments = getPropertyAssignmentsForWrites(writes); + if (node.expression) { + if (!returnValueProperty) { + returnValueProperty = "__return"; + } + assignments.unshift(ts.createPropertyAssignment(returnValueProperty, ts.visitNode(node.expression, visitor))); + } + if (assignments.length === 1) { + return ts.createReturn(assignments[0].name); + } + else { + return ts.createReturn(ts.createObjectLiteral(assignments)); + } + } + else { + var substitution = substitutions.get(ts.getNodeId(node).toString()); + return substitution || ts.visitEachChild(node, visitor, ts.nullTransformationContext); + } } } - extractMethod_1.extractFunctionInScope = extractFunctionInScope; - function isModuleBlock(n) { - return n.kind === 234; + function getPropertyAssignmentsForWrites(writes) { + return writes.map(function (w) { return ts.createShorthandPropertyAssignment(w.symbol.name); }); } function isReadonlyArray(v) { return ts.isArray(v); @@ -81209,8 +81218,8 @@ var ts; { start: start, end: end, text: text, code: code, category: category, source: source }; } function allEditsBeforePos(edits, pos) { - for (var _i = 0, edits_1 = edits; _i < edits_1.length; _i++) { - var edit = edits_1[_i]; + for (var _i = 0, edits_2 = edits; _i < edits_2.length; _i++) { + var edit = edits_2[_i]; if (ts.textSpanEnd(edit.span) >= pos) { return false; } @@ -82372,12 +82381,12 @@ var ts; return undefined; } if (simplifiedResult) { - var span_17 = helpItems.applicableSpan; + var span_18 = helpItems.applicableSpan; return { items: helpItems.items, applicableSpan: { - start: scriptInfo.positionToLineOffset(span_17.start), - end: scriptInfo.positionToLineOffset(span_17.start + span_17.length) + start: scriptInfo.positionToLineOffset(span_18.start), + end: scriptInfo.positionToLineOffset(span_18.start + span_18.length) }, selectedItemIndex: helpItems.selectedItemIndex, argumentIndex: helpItems.argumentIndex, diff --git a/lib/tsserverlibrary.d.ts b/lib/tsserverlibrary.d.ts index 45701530600..4779895cbab 100644 --- a/lib/tsserverlibrary.d.ts +++ b/lib/tsserverlibrary.d.ts @@ -3283,8 +3283,8 @@ declare namespace ts { }; type RefactorEditInfo = { edits: FileTextChanges[]; - renameFilename?: string; - renameLocation?: number; + renameFilename: string | undefined; + renameLocation: number | undefined; }; interface TextInsertion { newText: string; diff --git a/lib/tsserverlibrary.js b/lib/tsserverlibrary.js index bd74f810fb5..93916b40bff 100644 --- a/lib/tsserverlibrary.js +++ b/lib/tsserverlibrary.js @@ -12155,7 +12155,8 @@ var ts; return token() === 24 || isIdentifierOrPattern() || ts.isModifierKind(token()) || - token() === 57 || isStartOfType(); + token() === 57 || + isStartOfType(true); } function parseParameter() { var node = createNode(146); @@ -12494,7 +12495,7 @@ var ts; return parseTypeReference(); } } - function isStartOfType() { + function isStartOfType(inStartOfParameter) { switch (token()) { case 119: case 136: @@ -12521,9 +12522,9 @@ var ts; case 39: return true; case 38: - return lookAhead(nextTokenIsNumericLiteral); + return !inStartOfParameter && lookAhead(nextTokenIsNumericLiteral); case 19: - return lookAhead(isStartOfParenthesizedOrFunctionType); + return !inStartOfParameter && lookAhead(isStartOfParenthesizedOrFunctionType); default: return isIdentifier(); } @@ -74425,7 +74426,9 @@ var ts; deleteCallback(); } return { - edits: changeTracker.getChanges() + edits: changeTracker.getChanges(), + renameFilename: undefined, + renameLocation: undefined, }; function deleteNode(node, inList) { if (inList === void 0) { inList = false; } @@ -74576,11 +74579,11 @@ var ts; var usedNames = ts.createMap(); var i = 0; for (var _i = 0, extractions_1 = extractions; _i < extractions_1.length; _i++) { - var extr = extractions_1[_i]; - if (extr.errors && extr.errors.length) { + var _a = extractions_1[_i], scopeDescription = _a.scopeDescription, errors = _a.errors; + if (errors.length) { continue; } - var description = ts.formatStringFromArgs(ts.Diagnostics.Extract_function_into_0.message, [extr.scopeDescription]); + var description = ts.formatStringFromArgs(ts.Diagnostics.Extract_function_into_0.message, [scopeDescription]); if (!usedNames.has(description)) { usedNames.set(description, true); actions.push({ @@ -74608,9 +74611,7 @@ var ts; ts.Debug.assert(!!parsedIndexMatch, "Scope name should have matched the regexp"); var index = +parsedIndexMatch[1]; ts.Debug.assert(isFinite(index), "Expected to parse a finite number from the scope index"); - var extractions = getPossibleExtractions(targetRange, context, index); - ts.Debug.assert(extractions !== undefined, "The extraction went missing? How?"); - return ({ edits: extractions[0].changes }); + return getExtractionAtIndex(targetRange, context, index); } var Messages; (function (Messages) { @@ -74639,7 +74640,7 @@ var ts; RangeFacts[RangeFacts["IsAsyncFunction"] = 4] = "IsAsyncFunction"; RangeFacts[RangeFacts["UsesThis"] = 8] = "UsesThis"; RangeFacts[RangeFacts["InStaticRegion"] = 16] = "InStaticRegion"; - })(RangeFacts = extractMethod_1.RangeFacts || (extractMethod_1.RangeFacts = {})); + })(RangeFacts || (RangeFacts = {})); function getRangeToExtract(sourceFile, span) { var length = span.length || 0; var start = getParentNodeInSpan(ts.getTokenAtPosition(sourceFile, span.start, false), sourceFile, span); @@ -74879,7 +74880,7 @@ var ts; return undefined; } function isValidExtractionTarget(node) { - return (node.kind === 228) || ts.isSourceFile(node) || isModuleBlock(node) || ts.isClassLike(node); + return (node.kind === 228) || ts.isSourceFile(node) || ts.isModuleBlock(node) || ts.isClassLike(node); } function collectEnclosingScopes(range) { var current = isReadonlyArray(range.range) ? ts.firstOrUndefined(range.range) : range.range; @@ -74904,9 +74905,21 @@ var ts; } return scopes; } - extractMethod_1.collectEnclosingScopes = collectEnclosingScopes; - function getPossibleExtractions(targetRange, context, requestedChangesIndex) { - if (requestedChangesIndex === void 0) { requestedChangesIndex = undefined; } + function getExtractionAtIndex(targetRange, context, requestedChangesIndex) { + var _a = getPossibleExtractionsWorker(targetRange, context), scopes = _a.scopes, _b = _a.readsAndWrites, target = _b.target, usagesPerScope = _b.usagesPerScope, errorsPerScope = _b.errorsPerScope; + ts.Debug.assert(!errorsPerScope[requestedChangesIndex].length, "The extraction went missing? How?"); + context.cancellationToken.throwIfCancellationRequested(); + return extractFunctionInScope(target, scopes[requestedChangesIndex], usagesPerScope[requestedChangesIndex], targetRange, context); + } + extractMethod_1.getExtractionAtIndex = getExtractionAtIndex; + function getPossibleExtractions(targetRange, context) { + var extractions = getPossibleExtractionsWorker(targetRange, context); + return extractions && extractions.scopes.map(function (scope, i) { + return ({ scopeDescription: getDescriptionForScope(scope), errors: extractions.readsAndWrites.errorsPerScope[i] }); + }); + } + extractMethod_1.getPossibleExtractions = getPossibleExtractions; + function getPossibleExtractionsWorker(targetRange, context) { var sourceFile = context.file; if (targetRange === undefined) { return undefined; @@ -74916,29 +74929,9 @@ var ts; return undefined; } var enclosingTextRange = getEnclosingTextRange(targetRange, sourceFile); - var _a = collectReadsAndWrites(targetRange, scopes, enclosingTextRange, sourceFile, context.program.getTypeChecker()), target = _a.target, usagesPerScope = _a.usagesPerScope, errorsPerScope = _a.errorsPerScope; - context.cancellationToken.throwIfCancellationRequested(); - if (requestedChangesIndex !== undefined) { - if (errorsPerScope[requestedChangesIndex].length) { - return undefined; - } - return [extractFunctionInScope(target, scopes[requestedChangesIndex], usagesPerScope[requestedChangesIndex], targetRange, context)]; - } - else { - return scopes.map(function (scope, i) { - var errors = errorsPerScope[i]; - if (errors.length) { - return { - scope: scope, - scopeDescription: getDescriptionForScope(scope), - errors: errors - }; - } - return { scope: scope, scopeDescription: getDescriptionForScope(scope) }; - }); - } + var readsAndWrites = collectReadsAndWrites(targetRange, scopes, enclosingTextRange, sourceFile, context.program.getTypeChecker()); + return { scopes: scopes, readsAndWrites: readsAndWrites }; } - extractMethod_1.getPossibleExtractions = getPossibleExtractions; function getDescriptionForScope(scope) { if (ts.isFunctionLike(scope)) { switch (scope.kind) { @@ -74960,7 +74953,7 @@ var ts; return "'set " + scope.name.getText() + "'"; } } - else if (isModuleBlock(scope)) { + else if (ts.isModuleBlock(scope)) { return "namespace '" + scope.parent.name.getText() + "'"; } else if (ts.isClassLike(scope)) { @@ -74977,14 +74970,10 @@ var ts; return "unknown"; } } - function getUniqueName(isNameOkay) { + function getUniqueName(fileText) { var functionNameText = "newFunction"; - if (isNameOkay(functionNameText)) { - return functionNameText; - } - var i = 1; - while (!isNameOkay(functionNameText = "newFunction_" + i)) { - i++; + for (var i = 1; fileText.indexOf(functionNameText) !== -1; i++) { + functionNameText = "newFunction_" + i; } return functionNameText; } @@ -74992,10 +74981,9 @@ var ts; var usagesInScope = _a.usages, substitutions = _a.substitutions; var checker = context.program.getTypeChecker(); var file = scope.getSourceFile(); - var functionNameText = getUniqueName(function (n) { return !file.identifiers.has(n); }); + var functionNameText = getUniqueName(file.text); var isJS = ts.isInJavaScriptFile(scope); var functionName = ts.createIdentifier(functionNameText); - var functionReference = ts.createIdentifier(functionNameText); var returnType = undefined; var parameters = []; var callArguments = []; @@ -75018,7 +75006,7 @@ var ts; var contextualType = checker.getContextualType(node); returnType = checker.typeToTypeNode(contextualType); } - var _b = transformFunctionBody(node), body = _b.body, returnValueProperty = _b.returnValueProperty; + var _b = transformFunctionBody(node, writes, substitutions, !!(range.facts & RangeFacts.HasReturn)), body = _b.body, returnValueProperty = _b.returnValueProperty; var newFunction; if (ts.isClassLike(scope)) { var modifiers = isJS ? [] : [ts.createToken(112)]; @@ -75036,7 +75024,8 @@ var ts; var changeTracker = ts.textChanges.ChangeTracker.fromCodeFixContext(context); changeTracker.insertNodeBefore(context.file, scope.getLastToken(), newFunction, { prefix: context.newLineCharacter, suffix: context.newLineCharacter }); var newNodes = []; - var call = ts.createCall(ts.isClassLike(scope) ? ts.createPropertyAccess(range.facts & RangeFacts.InStaticRegion ? ts.createIdentifier(scope.name.getText()) : ts.createThis(), functionReference) : functionReference, undefined, callArguments); + var called = getCalledExpression(scope, range, functionNameText); + var call = ts.createCall(called, undefined, callArguments); if (range.facts & RangeFacts.IsGenerator) { call = ts.createYield(ts.createToken(39), call); } @@ -75086,65 +75075,85 @@ var ts; else { changeTracker.replaceNodeWithNodes(context.file, range.range, newNodes, { nodeSeparator: context.newLineCharacter }); } - return { - scope: scope, - scopeDescription: getDescriptionForScope(scope), - changes: changeTracker.getChanges() - }; - function getPropertyAssignmentsForWrites(writes) { - return writes.map(function (w) { return ts.createShorthandPropertyAssignment(w.symbol.name); }); - } - function generateReturnValueProperty() { - return "__return"; - } - function transformFunctionBody(body) { - if (ts.isBlock(body) && !writes && substitutions.size === 0) { - return { body: ts.createBlock(body.statements, true), returnValueProperty: undefined }; - } - var returnValueProperty; - var statements = ts.createNodeArray(ts.isBlock(body) ? body.statements.slice(0) : [ts.isStatement(body) ? body : ts.createReturn(body)]); - if (writes || substitutions.size) { - var rewrittenStatements = ts.visitNodes(statements, visitor).slice(); - if (writes && !(range.facts & RangeFacts.HasReturn) && ts.isStatement(body)) { - var assignments = getPropertyAssignmentsForWrites(writes); - if (assignments.length === 1) { - rewrittenStatements.push(ts.createReturn(assignments[0].name)); - } - else { - rewrittenStatements.push(ts.createReturn(ts.createObjectLiteral(assignments))); - } + var edits = changeTracker.getChanges(); + var renameRange = isReadonlyArray(range.range) ? range.range[0] : range.range; + var renameFilename = renameRange.getSourceFile().fileName; + var renameLocation = getRenameLocation(edits, renameFilename, functionNameText); + return { renameFilename: renameFilename, renameLocation: renameLocation, edits: edits }; + } + function getRenameLocation(edits, renameFilename, functionNameText) { + var delta = 0; + for (var _i = 0, edits_1 = edits; _i < edits_1.length; _i++) { + var _a = edits_1[_i], fileName = _a.fileName, textChanges_1 = _a.textChanges; + ts.Debug.assert(fileName === renameFilename); + for (var _b = 0, textChanges_2 = textChanges_1; _b < textChanges_2.length; _b++) { + var change = textChanges_2[_b]; + var span_17 = change.span, newText = change.newText; + var index = newText.indexOf(functionNameText); + if (index !== -1) { + return span_17.start + delta + index; } - return { body: ts.createBlock(rewrittenStatements, true), returnValueProperty: returnValueProperty }; + delta += newText.length - span_17.length; } - else { - return { body: ts.createBlock(statements, true), returnValueProperty: undefined }; - } - function visitor(node) { - if (node.kind === 219 && writes) { - var assignments = getPropertyAssignmentsForWrites(writes); - if (node.expression) { - if (!returnValueProperty) { - returnValueProperty = generateReturnValueProperty(); - } - assignments.unshift(ts.createPropertyAssignment(returnValueProperty, ts.visitNode(node.expression, visitor))); - } - if (assignments.length === 1) { - return ts.createReturn(assignments[0].name); - } - else { - return ts.createReturn(ts.createObjectLiteral(assignments)); - } + } + throw new Error(); + } + function getCalledExpression(scope, range, functionNameText) { + var functionReference = ts.createIdentifier(functionNameText); + if (ts.isClassLike(scope)) { + var lhs = range.facts & RangeFacts.InStaticRegion ? ts.createIdentifier(scope.name.text) : ts.createThis(); + return ts.createPropertyAccess(lhs, functionReference); + } + else { + return functionReference; + } + } + function transformFunctionBody(body, writes, substitutions, hasReturn) { + if (ts.isBlock(body) && !writes && substitutions.size === 0) { + return { body: ts.createBlock(body.statements, true), returnValueProperty: undefined }; + } + var returnValueProperty; + var statements = ts.createNodeArray(ts.isBlock(body) ? body.statements.slice(0) : [ts.isStatement(body) ? body : ts.createReturn(body)]); + if (writes || substitutions.size) { + var rewrittenStatements = ts.visitNodes(statements, visitor).slice(); + if (writes && !hasReturn && ts.isStatement(body)) { + var assignments = getPropertyAssignmentsForWrites(writes); + if (assignments.length === 1) { + rewrittenStatements.push(ts.createReturn(assignments[0].name)); } else { - var substitution = substitutions.get(ts.getNodeId(node).toString()); - return substitution || ts.visitEachChild(node, visitor, ts.nullTransformationContext); + rewrittenStatements.push(ts.createReturn(ts.createObjectLiteral(assignments))); } } + return { body: ts.createBlock(rewrittenStatements, true), returnValueProperty: returnValueProperty }; + } + else { + return { body: ts.createBlock(statements, true), returnValueProperty: undefined }; + } + function visitor(node) { + if (node.kind === 219 && writes) { + var assignments = getPropertyAssignmentsForWrites(writes); + if (node.expression) { + if (!returnValueProperty) { + returnValueProperty = "__return"; + } + assignments.unshift(ts.createPropertyAssignment(returnValueProperty, ts.visitNode(node.expression, visitor))); + } + if (assignments.length === 1) { + return ts.createReturn(assignments[0].name); + } + else { + return ts.createReturn(ts.createObjectLiteral(assignments)); + } + } + else { + var substitution = substitutions.get(ts.getNodeId(node).toString()); + return substitution || ts.visitEachChild(node, visitor, ts.nullTransformationContext); + } } } - extractMethod_1.extractFunctionInScope = extractFunctionInScope; - function isModuleBlock(n) { - return n.kind === 234; + function getPropertyAssignmentsForWrites(writes) { + return writes.map(function (w) { return ts.createShorthandPropertyAssignment(w.symbol.name); }); } function isReadonlyArray(v) { return ts.isArray(v); @@ -77374,8 +77383,8 @@ var ts; { start: start, end: end, text: text, code: code, category: category, source: source }; } function allEditsBeforePos(edits, pos) { - for (var _i = 0, edits_1 = edits; _i < edits_1.length; _i++) { - var edit = edits_1[_i]; + for (var _i = 0, edits_2 = edits; _i < edits_2.length; _i++) { + var edit = edits_2[_i]; if (ts.textSpanEnd(edit.span) >= pos) { return false; } @@ -78537,12 +78546,12 @@ var ts; return undefined; } if (simplifiedResult) { - var span_17 = helpItems.applicableSpan; + var span_18 = helpItems.applicableSpan; return { items: helpItems.items, applicableSpan: { - start: scriptInfo.positionToLineOffset(span_17.start), - end: scriptInfo.positionToLineOffset(span_17.start + span_17.length) + start: scriptInfo.positionToLineOffset(span_18.start), + end: scriptInfo.positionToLineOffset(span_18.start + span_18.length) }, selectedItemIndex: helpItems.selectedItemIndex, argumentIndex: helpItems.argumentIndex, diff --git a/lib/typescript.d.ts b/lib/typescript.d.ts index eaf4400c8ee..4b62e695c82 100644 --- a/lib/typescript.d.ts +++ b/lib/typescript.d.ts @@ -3935,8 +3935,8 @@ declare namespace ts { */ type RefactorEditInfo = { edits: FileTextChanges[]; - renameFilename?: string; - renameLocation?: number; + renameFilename: string | undefined; + renameLocation: number | undefined; }; interface TextInsertion { newText: string; diff --git a/lib/typescript.js b/lib/typescript.js index 7e2e649341e..74272714529 100644 --- a/lib/typescript.js +++ b/lib/typescript.js @@ -13845,7 +13845,8 @@ var ts; return token() === 24 /* DotDotDotToken */ || isIdentifierOrPattern() || ts.isModifierKind(token()) || - token() === 57 /* AtToken */ || isStartOfType(); + token() === 57 /* AtToken */ || + isStartOfType(/*inStartOfParameter*/ true); } function parseParameter() { var node = createNode(146 /* Parameter */); @@ -14252,7 +14253,7 @@ var ts; return parseTypeReference(); } } - function isStartOfType() { + function isStartOfType(inStartOfParameter) { switch (token()) { case 119 /* AnyKeyword */: case 136 /* StringKeyword */: @@ -14279,11 +14280,11 @@ var ts; case 39 /* AsteriskToken */: return true; case 38 /* MinusToken */: - return lookAhead(nextTokenIsNumericLiteral); + return !inStartOfParameter && lookAhead(nextTokenIsNumericLiteral); case 19 /* OpenParenToken */: // Only consider '(' the start of a type if followed by ')', '...', an identifier, a modifier, // or something that starts a type. We don't want to consider things like '(1)' a type. - return lookAhead(isStartOfParenthesizedOrFunctionType); + return !inStartOfParameter && lookAhead(isStartOfParenthesizedOrFunctionType); default: return isIdentifier(); } @@ -89385,7 +89386,9 @@ var ts; deleteCallback(); } return { - edits: changeTracker.getChanges() + edits: changeTracker.getChanges(), + renameFilename: undefined, + renameLocation: undefined, }; function deleteNode(node, inList) { if (inList === void 0) { inList = false; } @@ -89561,15 +89564,15 @@ var ts; var usedNames = ts.createMap(); var i = 0; for (var _i = 0, extractions_1 = extractions; _i < extractions_1.length; _i++) { - var extr = extractions_1[_i]; + var _a = extractions_1[_i], scopeDescription = _a.scopeDescription, errors = _a.errors; // Skip these since we don't have a way to report errors yet - if (extr.errors && extr.errors.length) { + if (errors.length) { continue; } // Don't issue refactorings with duplicated names. // Scopes come back in "innermost first" order, so extractions will // preferentially go into nearer scopes - var description = ts.formatStringFromArgs(ts.Diagnostics.Extract_function_into_0.message, [extr.scopeDescription]); + var description = ts.formatStringFromArgs(ts.Diagnostics.Extract_function_into_0.message, [scopeDescription]); if (!usedNames.has(description)) { usedNames.set(description, true); actions.push({ @@ -89599,10 +89602,7 @@ var ts; ts.Debug.assert(!!parsedIndexMatch, "Scope name should have matched the regexp"); var index = +parsedIndexMatch[1]; ts.Debug.assert(isFinite(index), "Expected to parse a finite number from the scope index"); - var extractions = getPossibleExtractions(targetRange, context, index); - // Scope is no longer valid from when the user issued the refactor (??) - ts.Debug.assert(extractions !== undefined, "The extraction went missing? How?"); - return ({ edits: extractions[0].changes }); + return getExtractionAtIndex(targetRange, context, index); } // Move these into diagnostic messages if they become user-facing var Messages; @@ -89635,13 +89635,14 @@ var ts; * The range is in a function which needs the 'static' modifier in a class */ RangeFacts[RangeFacts["InStaticRegion"] = 16] = "InStaticRegion"; - })(RangeFacts = extractMethod_1.RangeFacts || (extractMethod_1.RangeFacts = {})); + })(RangeFacts || (RangeFacts = {})); /** * getRangeToExtract takes a span inside a text file and returns either an expression or an array * of statements representing the minimum set of nodes needed to extract the entire span. This * process may fail, in which case a set of errors is returned instead (these are currently * not shown to the user, but can be used by us diagnostically) */ + // exported only for tests function getRangeToExtract(sourceFile, span) { var length = span.length || 0; // Walk up starting from the the start position until we find a non-SourceFile node that subsumes the selected span. @@ -89927,7 +89928,7 @@ var ts; } function isValidExtractionTarget(node) { // Note that we don't use isFunctionLike because we don't want to put the extracted closure *inside* a method - return (node.kind === 228 /* FunctionDeclaration */) || ts.isSourceFile(node) || isModuleBlock(node) || ts.isClassLike(node); + return (node.kind === 228 /* FunctionDeclaration */) || ts.isSourceFile(node) || ts.isModuleBlock(node) || ts.isClassLike(node); } /** * Computes possible places we could extract the function into. For example, @@ -89965,14 +89966,29 @@ var ts; } return scopes; } - extractMethod_1.collectEnclosingScopes = collectEnclosingScopes; + // exported only for tests + function getExtractionAtIndex(targetRange, context, requestedChangesIndex) { + var _a = getPossibleExtractionsWorker(targetRange, context), scopes = _a.scopes, _b = _a.readsAndWrites, target = _b.target, usagesPerScope = _b.usagesPerScope, errorsPerScope = _b.errorsPerScope; + ts.Debug.assert(!errorsPerScope[requestedChangesIndex].length, "The extraction went missing? How?"); + context.cancellationToken.throwIfCancellationRequested(); + return extractFunctionInScope(target, scopes[requestedChangesIndex], usagesPerScope[requestedChangesIndex], targetRange, context); + } + extractMethod_1.getExtractionAtIndex = getExtractionAtIndex; /** * Given a piece of text to extract ('targetRange'), computes a list of possible extractions. * Each returned ExtractResultForScope corresponds to a possible target scope and is either a set of changes * or an error explaining why we can't extract into that scope. */ - function getPossibleExtractions(targetRange, context, requestedChangesIndex) { - if (requestedChangesIndex === void 0) { requestedChangesIndex = undefined; } + // exported only for tests + function getPossibleExtractions(targetRange, context) { + var extractions = getPossibleExtractionsWorker(targetRange, context); + // Need the inner type annotation to avoid https://github.com/Microsoft/TypeScript/issues/7547 + return extractions && extractions.scopes.map(function (scope, i) { + return ({ scopeDescription: getDescriptionForScope(scope), errors: extractions.readsAndWrites.errorsPerScope[i] }); + }); + } + extractMethod_1.getPossibleExtractions = getPossibleExtractions; + function getPossibleExtractionsWorker(targetRange, context) { var sourceFile = context.file; if (targetRange === undefined) { return undefined; @@ -89982,29 +89998,9 @@ var ts; return undefined; } var enclosingTextRange = getEnclosingTextRange(targetRange, sourceFile); - var _a = collectReadsAndWrites(targetRange, scopes, enclosingTextRange, sourceFile, context.program.getTypeChecker()), target = _a.target, usagesPerScope = _a.usagesPerScope, errorsPerScope = _a.errorsPerScope; - context.cancellationToken.throwIfCancellationRequested(); - if (requestedChangesIndex !== undefined) { - if (errorsPerScope[requestedChangesIndex].length) { - return undefined; - } - return [extractFunctionInScope(target, scopes[requestedChangesIndex], usagesPerScope[requestedChangesIndex], targetRange, context)]; - } - else { - return scopes.map(function (scope, i) { - var errors = errorsPerScope[i]; - if (errors.length) { - return { - scope: scope, - scopeDescription: getDescriptionForScope(scope), - errors: errors - }; - } - return { scope: scope, scopeDescription: getDescriptionForScope(scope) }; - }); - } + var readsAndWrites = collectReadsAndWrites(targetRange, scopes, enclosingTextRange, sourceFile, context.program.getTypeChecker()); + return { scopes: scopes, readsAndWrites: readsAndWrites }; } - extractMethod_1.getPossibleExtractions = getPossibleExtractions; function getDescriptionForScope(scope) { if (ts.isFunctionLike(scope)) { switch (scope.kind) { @@ -90026,7 +90022,7 @@ var ts; return "'set " + scope.name.getText() + "'"; } } - else if (isModuleBlock(scope)) { + else if (ts.isModuleBlock(scope)) { return "namespace '" + scope.parent.name.getText() + "'"; } else if (ts.isClassLike(scope)) { @@ -90043,26 +90039,25 @@ var ts; return "unknown"; } } - function getUniqueName(isNameOkay) { + function getUniqueName(fileText) { var functionNameText = "newFunction"; - if (isNameOkay(functionNameText)) { - return functionNameText; - } - var i = 1; - while (!isNameOkay(functionNameText = "newFunction_" + i)) { - i++; + for (var i = 1; fileText.indexOf(functionNameText) !== -1; i++) { + functionNameText = "newFunction_" + i; } return functionNameText; } + /** + * Result of 'extractRange' operation for a specific scope. + * Stores either a list of changes that should be applied to extract a range or a list of errors + */ function extractFunctionInScope(node, scope, _a, range, context) { var usagesInScope = _a.usages, substitutions = _a.substitutions; var checker = context.program.getTypeChecker(); // Make a unique name for the extracted function var file = scope.getSourceFile(); - var functionNameText = getUniqueName(function (n) { return !file.identifiers.has(n); }); + var functionNameText = getUniqueName(file.text); var isJS = ts.isInJavaScriptFile(scope); var functionName = ts.createIdentifier(functionNameText); - var functionReference = ts.createIdentifier(functionNameText); var returnType = undefined; var parameters = []; var callArguments = []; @@ -90093,7 +90088,7 @@ var ts; var contextualType = checker.getContextualType(node); returnType = checker.typeToTypeNode(contextualType); } - var _b = transformFunctionBody(node), body = _b.body, returnValueProperty = _b.returnValueProperty; + var _b = transformFunctionBody(node, writes, substitutions, !!(range.facts & RangeFacts.HasReturn)), body = _b.body, returnValueProperty = _b.returnValueProperty; var newFunction; if (ts.isClassLike(scope)) { // always create private method in TypeScript files @@ -90119,7 +90114,8 @@ var ts; changeTracker.insertNodeBefore(context.file, scope.getLastToken(), newFunction, { prefix: context.newLineCharacter, suffix: context.newLineCharacter }); var newNodes = []; // replace range with function call - var call = ts.createCall(ts.isClassLike(scope) ? ts.createPropertyAccess(range.facts & RangeFacts.InStaticRegion ? ts.createIdentifier(scope.name.getText()) : ts.createThis(), functionReference) : functionReference, + var called = getCalledExpression(scope, range, functionNameText); + var call = ts.createCall(called, /*typeArguments*/ undefined, callArguments); if (range.facts & RangeFacts.IsGenerator) { call = ts.createYield(ts.createToken(39 /* AsteriskToken */), call); @@ -90176,69 +90172,92 @@ var ts; else { changeTracker.replaceNodeWithNodes(context.file, range.range, newNodes, { nodeSeparator: context.newLineCharacter }); } - return { - scope: scope, - scopeDescription: getDescriptionForScope(scope), - changes: changeTracker.getChanges() - }; - function getPropertyAssignmentsForWrites(writes) { - return writes.map(function (w) { return ts.createShorthandPropertyAssignment(w.symbol.name); }); - } - function generateReturnValueProperty() { - return "__return"; - } - function transformFunctionBody(body) { - if (ts.isBlock(body) && !writes && substitutions.size === 0) { - // already block, no writes to propagate back, no substitutions - can use node as is - return { body: ts.createBlock(body.statements, /*multLine*/ true), returnValueProperty: undefined }; - } - var returnValueProperty; - var statements = ts.createNodeArray(ts.isBlock(body) ? body.statements.slice(0) : [ts.isStatement(body) ? body : ts.createReturn(body)]); - // rewrite body if either there are writes that should be propagated back via return statements or there are substitutions - if (writes || substitutions.size) { - var rewrittenStatements = ts.visitNodes(statements, visitor).slice(); - if (writes && !(range.facts & RangeFacts.HasReturn) && ts.isStatement(body)) { - // add return at the end to propagate writes back in case if control flow falls out of the function body - // it is ok to know that range has at least one return since it we only allow unconditional returns - var assignments = getPropertyAssignmentsForWrites(writes); - if (assignments.length === 1) { - rewrittenStatements.push(ts.createReturn(assignments[0].name)); - } - else { - rewrittenStatements.push(ts.createReturn(ts.createObjectLiteral(assignments))); - } + var edits = changeTracker.getChanges(); + var renameRange = isReadonlyArray(range.range) ? range.range[0] : range.range; + var renameFilename = renameRange.getSourceFile().fileName; + var renameLocation = getRenameLocation(edits, renameFilename, functionNameText); + return { renameFilename: renameFilename, renameLocation: renameLocation, edits: edits }; + } + function getRenameLocation(edits, renameFilename, functionNameText) { + var delta = 0; + for (var _i = 0, edits_1 = edits; _i < edits_1.length; _i++) { + var _a = edits_1[_i], fileName = _a.fileName, textChanges_1 = _a.textChanges; + ts.Debug.assert(fileName === renameFilename); + for (var _b = 0, textChanges_2 = textChanges_1; _b < textChanges_2.length; _b++) { + var change = textChanges_2[_b]; + var span_17 = change.span, newText = change.newText; + // TODO(acasey): We are assuming that the call expression comes before the function declaration, + // because we want the new cursor to be on the call expression, + // which is closer to where the user was before extracting the function. + var index = newText.indexOf(functionNameText); + if (index !== -1) { + return span_17.start + delta + index; } - return { body: ts.createBlock(rewrittenStatements, /*multiLine*/ true), returnValueProperty: returnValueProperty }; + delta += newText.length - span_17.length; } - else { - return { body: ts.createBlock(statements, /*multiLine*/ true), returnValueProperty: undefined }; - } - function visitor(node) { - if (node.kind === 219 /* ReturnStatement */ && writes) { - var assignments = getPropertyAssignmentsForWrites(writes); - if (node.expression) { - if (!returnValueProperty) { - returnValueProperty = generateReturnValueProperty(); - } - assignments.unshift(ts.createPropertyAssignment(returnValueProperty, ts.visitNode(node.expression, visitor))); - } - if (assignments.length === 1) { - return ts.createReturn(assignments[0].name); - } - else { - return ts.createReturn(ts.createObjectLiteral(assignments)); - } + } + throw new Error(); // Didn't find the text we inserted? + } + function getCalledExpression(scope, range, functionNameText) { + var functionReference = ts.createIdentifier(functionNameText); + if (ts.isClassLike(scope)) { + var lhs = range.facts & RangeFacts.InStaticRegion ? ts.createIdentifier(scope.name.text) : ts.createThis(); + return ts.createPropertyAccess(lhs, functionReference); + } + else { + return functionReference; + } + } + function transformFunctionBody(body, writes, substitutions, hasReturn) { + if (ts.isBlock(body) && !writes && substitutions.size === 0) { + // already block, no writes to propagate back, no substitutions - can use node as is + return { body: ts.createBlock(body.statements, /*multLine*/ true), returnValueProperty: undefined }; + } + var returnValueProperty; + var statements = ts.createNodeArray(ts.isBlock(body) ? body.statements.slice(0) : [ts.isStatement(body) ? body : ts.createReturn(body)]); + // rewrite body if either there are writes that should be propagated back via return statements or there are substitutions + if (writes || substitutions.size) { + var rewrittenStatements = ts.visitNodes(statements, visitor).slice(); + if (writes && !hasReturn && ts.isStatement(body)) { + // add return at the end to propagate writes back in case if control flow falls out of the function body + // it is ok to know that range has at least one return since it we only allow unconditional returns + var assignments = getPropertyAssignmentsForWrites(writes); + if (assignments.length === 1) { + rewrittenStatements.push(ts.createReturn(assignments[0].name)); } else { - var substitution = substitutions.get(ts.getNodeId(node).toString()); - return substitution || ts.visitEachChild(node, visitor, ts.nullTransformationContext); + rewrittenStatements.push(ts.createReturn(ts.createObjectLiteral(assignments))); } } + return { body: ts.createBlock(rewrittenStatements, /*multiLine*/ true), returnValueProperty: returnValueProperty }; + } + else { + return { body: ts.createBlock(statements, /*multiLine*/ true), returnValueProperty: undefined }; + } + function visitor(node) { + if (node.kind === 219 /* ReturnStatement */ && writes) { + var assignments = getPropertyAssignmentsForWrites(writes); + if (node.expression) { + if (!returnValueProperty) { + returnValueProperty = "__return"; + } + assignments.unshift(ts.createPropertyAssignment(returnValueProperty, ts.visitNode(node.expression, visitor))); + } + if (assignments.length === 1) { + return ts.createReturn(assignments[0].name); + } + else { + return ts.createReturn(ts.createObjectLiteral(assignments)); + } + } + else { + var substitution = substitutions.get(ts.getNodeId(node).toString()); + return substitution || ts.visitEachChild(node, visitor, ts.nullTransformationContext); + } } } - extractMethod_1.extractFunctionInScope = extractFunctionInScope; - function isModuleBlock(n) { - return n.kind === 234 /* ModuleBlock */; + function getPropertyAssignmentsForWrites(writes) { + return writes.map(function (w) { return ts.createShorthandPropertyAssignment(w.symbol.name); }); } function isReadonlyArray(v) { return ts.isArray(v); diff --git a/lib/typescriptServices.d.ts b/lib/typescriptServices.d.ts index 825ed9b5c0d..d5602ea0f78 100644 --- a/lib/typescriptServices.d.ts +++ b/lib/typescriptServices.d.ts @@ -3935,8 +3935,8 @@ declare namespace ts { */ type RefactorEditInfo = { edits: FileTextChanges[]; - renameFilename?: string; - renameLocation?: number; + renameFilename: string | undefined; + renameLocation: number | undefined; }; interface TextInsertion { newText: string; diff --git a/lib/typescriptServices.js b/lib/typescriptServices.js index 7e2e649341e..74272714529 100644 --- a/lib/typescriptServices.js +++ b/lib/typescriptServices.js @@ -13845,7 +13845,8 @@ var ts; return token() === 24 /* DotDotDotToken */ || isIdentifierOrPattern() || ts.isModifierKind(token()) || - token() === 57 /* AtToken */ || isStartOfType(); + token() === 57 /* AtToken */ || + isStartOfType(/*inStartOfParameter*/ true); } function parseParameter() { var node = createNode(146 /* Parameter */); @@ -14252,7 +14253,7 @@ var ts; return parseTypeReference(); } } - function isStartOfType() { + function isStartOfType(inStartOfParameter) { switch (token()) { case 119 /* AnyKeyword */: case 136 /* StringKeyword */: @@ -14279,11 +14280,11 @@ var ts; case 39 /* AsteriskToken */: return true; case 38 /* MinusToken */: - return lookAhead(nextTokenIsNumericLiteral); + return !inStartOfParameter && lookAhead(nextTokenIsNumericLiteral); case 19 /* OpenParenToken */: // Only consider '(' the start of a type if followed by ')', '...', an identifier, a modifier, // or something that starts a type. We don't want to consider things like '(1)' a type. - return lookAhead(isStartOfParenthesizedOrFunctionType); + return !inStartOfParameter && lookAhead(isStartOfParenthesizedOrFunctionType); default: return isIdentifier(); } @@ -89385,7 +89386,9 @@ var ts; deleteCallback(); } return { - edits: changeTracker.getChanges() + edits: changeTracker.getChanges(), + renameFilename: undefined, + renameLocation: undefined, }; function deleteNode(node, inList) { if (inList === void 0) { inList = false; } @@ -89561,15 +89564,15 @@ var ts; var usedNames = ts.createMap(); var i = 0; for (var _i = 0, extractions_1 = extractions; _i < extractions_1.length; _i++) { - var extr = extractions_1[_i]; + var _a = extractions_1[_i], scopeDescription = _a.scopeDescription, errors = _a.errors; // Skip these since we don't have a way to report errors yet - if (extr.errors && extr.errors.length) { + if (errors.length) { continue; } // Don't issue refactorings with duplicated names. // Scopes come back in "innermost first" order, so extractions will // preferentially go into nearer scopes - var description = ts.formatStringFromArgs(ts.Diagnostics.Extract_function_into_0.message, [extr.scopeDescription]); + var description = ts.formatStringFromArgs(ts.Diagnostics.Extract_function_into_0.message, [scopeDescription]); if (!usedNames.has(description)) { usedNames.set(description, true); actions.push({ @@ -89599,10 +89602,7 @@ var ts; ts.Debug.assert(!!parsedIndexMatch, "Scope name should have matched the regexp"); var index = +parsedIndexMatch[1]; ts.Debug.assert(isFinite(index), "Expected to parse a finite number from the scope index"); - var extractions = getPossibleExtractions(targetRange, context, index); - // Scope is no longer valid from when the user issued the refactor (??) - ts.Debug.assert(extractions !== undefined, "The extraction went missing? How?"); - return ({ edits: extractions[0].changes }); + return getExtractionAtIndex(targetRange, context, index); } // Move these into diagnostic messages if they become user-facing var Messages; @@ -89635,13 +89635,14 @@ var ts; * The range is in a function which needs the 'static' modifier in a class */ RangeFacts[RangeFacts["InStaticRegion"] = 16] = "InStaticRegion"; - })(RangeFacts = extractMethod_1.RangeFacts || (extractMethod_1.RangeFacts = {})); + })(RangeFacts || (RangeFacts = {})); /** * getRangeToExtract takes a span inside a text file and returns either an expression or an array * of statements representing the minimum set of nodes needed to extract the entire span. This * process may fail, in which case a set of errors is returned instead (these are currently * not shown to the user, but can be used by us diagnostically) */ + // exported only for tests function getRangeToExtract(sourceFile, span) { var length = span.length || 0; // Walk up starting from the the start position until we find a non-SourceFile node that subsumes the selected span. @@ -89927,7 +89928,7 @@ var ts; } function isValidExtractionTarget(node) { // Note that we don't use isFunctionLike because we don't want to put the extracted closure *inside* a method - return (node.kind === 228 /* FunctionDeclaration */) || ts.isSourceFile(node) || isModuleBlock(node) || ts.isClassLike(node); + return (node.kind === 228 /* FunctionDeclaration */) || ts.isSourceFile(node) || ts.isModuleBlock(node) || ts.isClassLike(node); } /** * Computes possible places we could extract the function into. For example, @@ -89965,14 +89966,29 @@ var ts; } return scopes; } - extractMethod_1.collectEnclosingScopes = collectEnclosingScopes; + // exported only for tests + function getExtractionAtIndex(targetRange, context, requestedChangesIndex) { + var _a = getPossibleExtractionsWorker(targetRange, context), scopes = _a.scopes, _b = _a.readsAndWrites, target = _b.target, usagesPerScope = _b.usagesPerScope, errorsPerScope = _b.errorsPerScope; + ts.Debug.assert(!errorsPerScope[requestedChangesIndex].length, "The extraction went missing? How?"); + context.cancellationToken.throwIfCancellationRequested(); + return extractFunctionInScope(target, scopes[requestedChangesIndex], usagesPerScope[requestedChangesIndex], targetRange, context); + } + extractMethod_1.getExtractionAtIndex = getExtractionAtIndex; /** * Given a piece of text to extract ('targetRange'), computes a list of possible extractions. * Each returned ExtractResultForScope corresponds to a possible target scope and is either a set of changes * or an error explaining why we can't extract into that scope. */ - function getPossibleExtractions(targetRange, context, requestedChangesIndex) { - if (requestedChangesIndex === void 0) { requestedChangesIndex = undefined; } + // exported only for tests + function getPossibleExtractions(targetRange, context) { + var extractions = getPossibleExtractionsWorker(targetRange, context); + // Need the inner type annotation to avoid https://github.com/Microsoft/TypeScript/issues/7547 + return extractions && extractions.scopes.map(function (scope, i) { + return ({ scopeDescription: getDescriptionForScope(scope), errors: extractions.readsAndWrites.errorsPerScope[i] }); + }); + } + extractMethod_1.getPossibleExtractions = getPossibleExtractions; + function getPossibleExtractionsWorker(targetRange, context) { var sourceFile = context.file; if (targetRange === undefined) { return undefined; @@ -89982,29 +89998,9 @@ var ts; return undefined; } var enclosingTextRange = getEnclosingTextRange(targetRange, sourceFile); - var _a = collectReadsAndWrites(targetRange, scopes, enclosingTextRange, sourceFile, context.program.getTypeChecker()), target = _a.target, usagesPerScope = _a.usagesPerScope, errorsPerScope = _a.errorsPerScope; - context.cancellationToken.throwIfCancellationRequested(); - if (requestedChangesIndex !== undefined) { - if (errorsPerScope[requestedChangesIndex].length) { - return undefined; - } - return [extractFunctionInScope(target, scopes[requestedChangesIndex], usagesPerScope[requestedChangesIndex], targetRange, context)]; - } - else { - return scopes.map(function (scope, i) { - var errors = errorsPerScope[i]; - if (errors.length) { - return { - scope: scope, - scopeDescription: getDescriptionForScope(scope), - errors: errors - }; - } - return { scope: scope, scopeDescription: getDescriptionForScope(scope) }; - }); - } + var readsAndWrites = collectReadsAndWrites(targetRange, scopes, enclosingTextRange, sourceFile, context.program.getTypeChecker()); + return { scopes: scopes, readsAndWrites: readsAndWrites }; } - extractMethod_1.getPossibleExtractions = getPossibleExtractions; function getDescriptionForScope(scope) { if (ts.isFunctionLike(scope)) { switch (scope.kind) { @@ -90026,7 +90022,7 @@ var ts; return "'set " + scope.name.getText() + "'"; } } - else if (isModuleBlock(scope)) { + else if (ts.isModuleBlock(scope)) { return "namespace '" + scope.parent.name.getText() + "'"; } else if (ts.isClassLike(scope)) { @@ -90043,26 +90039,25 @@ var ts; return "unknown"; } } - function getUniqueName(isNameOkay) { + function getUniqueName(fileText) { var functionNameText = "newFunction"; - if (isNameOkay(functionNameText)) { - return functionNameText; - } - var i = 1; - while (!isNameOkay(functionNameText = "newFunction_" + i)) { - i++; + for (var i = 1; fileText.indexOf(functionNameText) !== -1; i++) { + functionNameText = "newFunction_" + i; } return functionNameText; } + /** + * Result of 'extractRange' operation for a specific scope. + * Stores either a list of changes that should be applied to extract a range or a list of errors + */ function extractFunctionInScope(node, scope, _a, range, context) { var usagesInScope = _a.usages, substitutions = _a.substitutions; var checker = context.program.getTypeChecker(); // Make a unique name for the extracted function var file = scope.getSourceFile(); - var functionNameText = getUniqueName(function (n) { return !file.identifiers.has(n); }); + var functionNameText = getUniqueName(file.text); var isJS = ts.isInJavaScriptFile(scope); var functionName = ts.createIdentifier(functionNameText); - var functionReference = ts.createIdentifier(functionNameText); var returnType = undefined; var parameters = []; var callArguments = []; @@ -90093,7 +90088,7 @@ var ts; var contextualType = checker.getContextualType(node); returnType = checker.typeToTypeNode(contextualType); } - var _b = transformFunctionBody(node), body = _b.body, returnValueProperty = _b.returnValueProperty; + var _b = transformFunctionBody(node, writes, substitutions, !!(range.facts & RangeFacts.HasReturn)), body = _b.body, returnValueProperty = _b.returnValueProperty; var newFunction; if (ts.isClassLike(scope)) { // always create private method in TypeScript files @@ -90119,7 +90114,8 @@ var ts; changeTracker.insertNodeBefore(context.file, scope.getLastToken(), newFunction, { prefix: context.newLineCharacter, suffix: context.newLineCharacter }); var newNodes = []; // replace range with function call - var call = ts.createCall(ts.isClassLike(scope) ? ts.createPropertyAccess(range.facts & RangeFacts.InStaticRegion ? ts.createIdentifier(scope.name.getText()) : ts.createThis(), functionReference) : functionReference, + var called = getCalledExpression(scope, range, functionNameText); + var call = ts.createCall(called, /*typeArguments*/ undefined, callArguments); if (range.facts & RangeFacts.IsGenerator) { call = ts.createYield(ts.createToken(39 /* AsteriskToken */), call); @@ -90176,69 +90172,92 @@ var ts; else { changeTracker.replaceNodeWithNodes(context.file, range.range, newNodes, { nodeSeparator: context.newLineCharacter }); } - return { - scope: scope, - scopeDescription: getDescriptionForScope(scope), - changes: changeTracker.getChanges() - }; - function getPropertyAssignmentsForWrites(writes) { - return writes.map(function (w) { return ts.createShorthandPropertyAssignment(w.symbol.name); }); - } - function generateReturnValueProperty() { - return "__return"; - } - function transformFunctionBody(body) { - if (ts.isBlock(body) && !writes && substitutions.size === 0) { - // already block, no writes to propagate back, no substitutions - can use node as is - return { body: ts.createBlock(body.statements, /*multLine*/ true), returnValueProperty: undefined }; - } - var returnValueProperty; - var statements = ts.createNodeArray(ts.isBlock(body) ? body.statements.slice(0) : [ts.isStatement(body) ? body : ts.createReturn(body)]); - // rewrite body if either there are writes that should be propagated back via return statements or there are substitutions - if (writes || substitutions.size) { - var rewrittenStatements = ts.visitNodes(statements, visitor).slice(); - if (writes && !(range.facts & RangeFacts.HasReturn) && ts.isStatement(body)) { - // add return at the end to propagate writes back in case if control flow falls out of the function body - // it is ok to know that range has at least one return since it we only allow unconditional returns - var assignments = getPropertyAssignmentsForWrites(writes); - if (assignments.length === 1) { - rewrittenStatements.push(ts.createReturn(assignments[0].name)); - } - else { - rewrittenStatements.push(ts.createReturn(ts.createObjectLiteral(assignments))); - } + var edits = changeTracker.getChanges(); + var renameRange = isReadonlyArray(range.range) ? range.range[0] : range.range; + var renameFilename = renameRange.getSourceFile().fileName; + var renameLocation = getRenameLocation(edits, renameFilename, functionNameText); + return { renameFilename: renameFilename, renameLocation: renameLocation, edits: edits }; + } + function getRenameLocation(edits, renameFilename, functionNameText) { + var delta = 0; + for (var _i = 0, edits_1 = edits; _i < edits_1.length; _i++) { + var _a = edits_1[_i], fileName = _a.fileName, textChanges_1 = _a.textChanges; + ts.Debug.assert(fileName === renameFilename); + for (var _b = 0, textChanges_2 = textChanges_1; _b < textChanges_2.length; _b++) { + var change = textChanges_2[_b]; + var span_17 = change.span, newText = change.newText; + // TODO(acasey): We are assuming that the call expression comes before the function declaration, + // because we want the new cursor to be on the call expression, + // which is closer to where the user was before extracting the function. + var index = newText.indexOf(functionNameText); + if (index !== -1) { + return span_17.start + delta + index; } - return { body: ts.createBlock(rewrittenStatements, /*multiLine*/ true), returnValueProperty: returnValueProperty }; + delta += newText.length - span_17.length; } - else { - return { body: ts.createBlock(statements, /*multiLine*/ true), returnValueProperty: undefined }; - } - function visitor(node) { - if (node.kind === 219 /* ReturnStatement */ && writes) { - var assignments = getPropertyAssignmentsForWrites(writes); - if (node.expression) { - if (!returnValueProperty) { - returnValueProperty = generateReturnValueProperty(); - } - assignments.unshift(ts.createPropertyAssignment(returnValueProperty, ts.visitNode(node.expression, visitor))); - } - if (assignments.length === 1) { - return ts.createReturn(assignments[0].name); - } - else { - return ts.createReturn(ts.createObjectLiteral(assignments)); - } + } + throw new Error(); // Didn't find the text we inserted? + } + function getCalledExpression(scope, range, functionNameText) { + var functionReference = ts.createIdentifier(functionNameText); + if (ts.isClassLike(scope)) { + var lhs = range.facts & RangeFacts.InStaticRegion ? ts.createIdentifier(scope.name.text) : ts.createThis(); + return ts.createPropertyAccess(lhs, functionReference); + } + else { + return functionReference; + } + } + function transformFunctionBody(body, writes, substitutions, hasReturn) { + if (ts.isBlock(body) && !writes && substitutions.size === 0) { + // already block, no writes to propagate back, no substitutions - can use node as is + return { body: ts.createBlock(body.statements, /*multLine*/ true), returnValueProperty: undefined }; + } + var returnValueProperty; + var statements = ts.createNodeArray(ts.isBlock(body) ? body.statements.slice(0) : [ts.isStatement(body) ? body : ts.createReturn(body)]); + // rewrite body if either there are writes that should be propagated back via return statements or there are substitutions + if (writes || substitutions.size) { + var rewrittenStatements = ts.visitNodes(statements, visitor).slice(); + if (writes && !hasReturn && ts.isStatement(body)) { + // add return at the end to propagate writes back in case if control flow falls out of the function body + // it is ok to know that range has at least one return since it we only allow unconditional returns + var assignments = getPropertyAssignmentsForWrites(writes); + if (assignments.length === 1) { + rewrittenStatements.push(ts.createReturn(assignments[0].name)); } else { - var substitution = substitutions.get(ts.getNodeId(node).toString()); - return substitution || ts.visitEachChild(node, visitor, ts.nullTransformationContext); + rewrittenStatements.push(ts.createReturn(ts.createObjectLiteral(assignments))); } } + return { body: ts.createBlock(rewrittenStatements, /*multiLine*/ true), returnValueProperty: returnValueProperty }; + } + else { + return { body: ts.createBlock(statements, /*multiLine*/ true), returnValueProperty: undefined }; + } + function visitor(node) { + if (node.kind === 219 /* ReturnStatement */ && writes) { + var assignments = getPropertyAssignmentsForWrites(writes); + if (node.expression) { + if (!returnValueProperty) { + returnValueProperty = "__return"; + } + assignments.unshift(ts.createPropertyAssignment(returnValueProperty, ts.visitNode(node.expression, visitor))); + } + if (assignments.length === 1) { + return ts.createReturn(assignments[0].name); + } + else { + return ts.createReturn(ts.createObjectLiteral(assignments)); + } + } + else { + var substitution = substitutions.get(ts.getNodeId(node).toString()); + return substitution || ts.visitEachChild(node, visitor, ts.nullTransformationContext); + } } } - extractMethod_1.extractFunctionInScope = extractFunctionInScope; - function isModuleBlock(n) { - return n.kind === 234 /* ModuleBlock */; + function getPropertyAssignmentsForWrites(writes) { + return writes.map(function (w) { return ts.createShorthandPropertyAssignment(w.symbol.name); }); } function isReadonlyArray(v) { return ts.isArray(v); diff --git a/lib/typingsInstaller.js b/lib/typingsInstaller.js index 2da644fe8b8..2c2ccfdcf1f 100644 --- a/lib/typingsInstaller.js +++ b/lib/typingsInstaller.js @@ -11098,7 +11098,8 @@ var ts; return token() === 24 || isIdentifierOrPattern() || ts.isModifierKind(token()) || - token() === 57 || isStartOfType(); + token() === 57 || + isStartOfType(true); } function parseParameter() { var node = createNode(146); @@ -11437,7 +11438,7 @@ var ts; return parseTypeReference(); } } - function isStartOfType() { + function isStartOfType(inStartOfParameter) { switch (token()) { case 119: case 136: @@ -11464,9 +11465,9 @@ var ts; case 39: return true; case 38: - return lookAhead(nextTokenIsNumericLiteral); + return !inStartOfParameter && lookAhead(nextTokenIsNumericLiteral); case 19: - return lookAhead(isStartOfParenthesizedOrFunctionType); + return !inStartOfParameter && lookAhead(isStartOfParenthesizedOrFunctionType); default: return isIdentifier(); }