diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 4bd4801ce8f64..a47387a932e4b 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -917,7 +917,7 @@ namespace ts { /** * Deduplicates an unsorted array. - * @param equalityComparer An optional `EqualityComparer` used to determine if two values are duplicates. + * @param equalityComparer An `EqualityComparer` used to determine if two values are duplicates. * @param comparer An optional `Comparer` used to sort entries before comparison, though the * result will remain in the original order in `array`. */ diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 09b0f72129255..3a7c59aacfc43 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -4871,5 +4871,9 @@ "Enable the 'experimentalDecorators' option in your configuration file": { "category": "Message", "code": 95074 + }, + "Convert to named parameters": { + "category": "Message", + "code": 95075 } } diff --git a/src/services/codefixes/convertFunctionToEs6Class.ts b/src/services/codefixes/convertFunctionToEs6Class.ts index 78e10ce20449e..b87a78106ad76 100644 --- a/src/services/codefixes/convertFunctionToEs6Class.ts +++ b/src/services/codefixes/convertFunctionToEs6Class.ts @@ -35,7 +35,7 @@ namespace ts.codefix { precedingNode = ctorDeclaration.parent.parent; newClassDeclaration = createClassFromVariableDeclaration(ctorDeclaration as VariableDeclaration); if ((ctorDeclaration.parent).declarations.length === 1) { - copyComments(precedingNode, newClassDeclaration!, sourceFile); // TODO: GH#18217 + copyLeadingComments(precedingNode, newClassDeclaration!, sourceFile); // TODO: GH#18217 changes.delete(sourceFile, precedingNode); } else { @@ -48,7 +48,7 @@ namespace ts.codefix { return undefined; } - copyComments(ctorDeclaration, newClassDeclaration, sourceFile); + copyLeadingComments(ctorDeclaration, newClassDeclaration, sourceFile); // Because the preceding node could be touched, we need to insert nodes before delete nodes. changes.insertNodeAfter(sourceFile, precedingNode!, newClassDeclaration); @@ -112,7 +112,7 @@ namespace ts.codefix { const fullModifiers = concatenate(modifiers, getModifierKindFromSource(functionExpression, SyntaxKind.AsyncKeyword)); const method = createMethod(/*decorators*/ undefined, fullModifiers, /*asteriskToken*/ undefined, memberDeclaration.name, /*questionToken*/ undefined, /*typeParameters*/ undefined, functionExpression.parameters, /*type*/ undefined, functionExpression.body); - copyComments(assignmentBinaryExpression, method, sourceFile); + copyLeadingComments(assignmentBinaryExpression, method, sourceFile); return method; } @@ -132,7 +132,7 @@ namespace ts.codefix { const fullModifiers = concatenate(modifiers, getModifierKindFromSource(arrowFunction, SyntaxKind.AsyncKeyword)); const method = createMethod(/*decorators*/ undefined, fullModifiers, /*asteriskToken*/ undefined, memberDeclaration.name, /*questionToken*/ undefined, /*typeParameters*/ undefined, arrowFunction.parameters, /*type*/ undefined, bodyBlock); - copyComments(assignmentBinaryExpression, method, sourceFile); + copyLeadingComments(assignmentBinaryExpression, method, sourceFile); return method; } @@ -143,7 +143,7 @@ namespace ts.codefix { } const prop = createProperty(/*decorators*/ undefined, modifiers, memberDeclaration.name, /*questionToken*/ undefined, /*type*/ undefined, assignmentBinaryExpression.right); - copyComments(assignmentBinaryExpression.parent, prop, sourceFile); + copyLeadingComments(assignmentBinaryExpression.parent, prop, sourceFile); return prop; } } diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts index f47f618f79d64..ecf2841e249ef 100644 --- a/src/services/codefixes/inferFromUsage.ts +++ b/src/services/codefixes/inferFromUsage.ts @@ -304,30 +304,6 @@ namespace ts.codefix { } } - function getTypeNodeIfAccessible(type: Type, enclosingScope: Node, program: Program, host: LanguageServiceHost): TypeNode | undefined { - const checker = program.getTypeChecker(); - let typeIsAccessible = true; - const notAccessible = () => { typeIsAccessible = false; }; - const res = checker.typeToTypeNode(type, enclosingScope, /*flags*/ undefined, { - trackSymbol: (symbol, declaration, meaning) => { - // TODO: GH#18217 - typeIsAccessible = typeIsAccessible && checker.isSymbolAccessible(symbol, declaration, meaning!, /*shouldComputeAliasToMarkVisible*/ false).accessibility === SymbolAccessibility.Accessible; - }, - reportInaccessibleThisError: notAccessible, - reportPrivateInBaseOfClassExpression: notAccessible, - reportInaccessibleUniqueSymbolError: notAccessible, - moduleResolverHost: { - readFile: host.readFile, - fileExists: host.fileExists, - directoryExists: host.directoryExists, - getSourceFiles: program.getSourceFiles, - getCurrentDirectory: program.getCurrentDirectory, - getCommonSourceDirectory: program.getCommonSourceDirectory, - } - }); - return typeIsAccessible ? res : undefined; - } - function getReferences(token: PropertyName | Token, program: Program, cancellationToken: CancellationToken): ReadonlyArray { // Position shouldn't matter since token is not a SourceFile. return mapDefined(FindAllReferences.getReferenceEntriesForNode(-1, token, program, program.getSourceFiles(), cancellationToken), entry => diff --git a/src/services/organizeImports.ts b/src/services/organizeImports.ts index 157ba98e26225..5bfa0c74ab131 100644 --- a/src/services/organizeImports.ts +++ b/src/services/organizeImports.ts @@ -68,8 +68,8 @@ namespace ts.OrganizeImports { else { // Note: Delete the surrounding trivia because it will have been retained in newImportDecls. changeTracker.replaceNodeWithNodes(sourceFile, oldImportDecls[0], newImportDecls, { - useNonAdjustedStartPosition: true, // Leave header comment in place - useNonAdjustedEndPosition: false, + leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, // Leave header comment in place + trailingTriviaOption: textChanges.TrailingTriviaOption.Include, suffix: getNewLineOrDefaultFromHost(host, formatContext.options), }); } diff --git a/src/services/refactors/addOrRemoveBracesToArrowFunction.ts b/src/services/refactors/addOrRemoveBracesToArrowFunction.ts index c5fccde13db6e..97d6f4d6667ba 100644 --- a/src/services/refactors/addOrRemoveBracesToArrowFunction.ts +++ b/src/services/refactors/addOrRemoveBracesToArrowFunction.ts @@ -48,13 +48,13 @@ namespace ts.refactor.addOrRemoveBracesToArrowFunction { const returnStatement = createReturn(expression); body = createBlock([returnStatement], /* multiLine */ true); suppressLeadingAndTrailingTrivia(body); - copyComments(expression!, returnStatement, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ true); + copyLeadingComments(expression!, returnStatement, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ true); } else if (actionName === removeBracesActionName && returnStatement) { const actualExpression = expression || createVoidZero(); body = needsParentheses(actualExpression) ? createParen(actualExpression) : actualExpression; suppressLeadingAndTrailingTrivia(body); - copyComments(returnStatement, body, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); + copyLeadingComments(returnStatement, body, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); } else { Debug.fail("invalid action"); diff --git a/src/services/refactors/convertToNamedParameters.ts b/src/services/refactors/convertToNamedParameters.ts new file mode 100644 index 0000000000000..61e9ccf981136 --- /dev/null +++ b/src/services/refactors/convertToNamedParameters.ts @@ -0,0 +1,520 @@ +/* @internal */ +namespace ts.refactor.convertToNamedParameters { + const refactorName = "Convert to named parameters"; + const minimumParameterLength = 2; + registerRefactor(refactorName, { getEditsForAction, getAvailableActions }); + + + function getAvailableActions(context: RefactorContext): ReadonlyArray { + const { file, startPosition } = context; + const isJSFile = isSourceFileJS(file); + if (isJSFile) return emptyArray; // TODO: GH#30113 + const functionDeclaration = getFunctionDeclarationAtPosition(file, startPosition, context.program.getTypeChecker()); + if (!functionDeclaration) return emptyArray; + + const description = getLocaleSpecificMessage(Diagnostics.Convert_to_named_parameters); + return [{ + name: refactorName, + description, + actions: [{ + name: refactorName, + description + }] + }]; + } + + function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { + Debug.assert(actionName === refactorName); + const { file, startPosition, program, cancellationToken, host } = context; + const functionDeclaration = getFunctionDeclarationAtPosition(file, startPosition, program.getTypeChecker()); + if (!functionDeclaration || !cancellationToken) return undefined; + + const groupedReferences = getGroupedReferences(functionDeclaration, program, cancellationToken); + if (groupedReferences.valid) { + const edits = textChanges.ChangeTracker.with(context, t => doChange(file, program, host, t, functionDeclaration, groupedReferences)); + return { renameFilename: undefined, renameLocation: undefined, edits }; + } + + return { edits: [] }; // TODO: GH#30113 + } + + function doChange( + sourceFile: SourceFile, + program: Program, + host: LanguageServiceHost, + changes: textChanges.ChangeTracker, + functionDeclaration: ValidFunctionDeclaration, + groupedReferences: GroupedReferences): void { + const newParamDeclaration = map(createNewParameters(functionDeclaration, program, host), param => getSynthesizedDeepClone(param)); + changes.replaceNodeRangeWithNodes( + sourceFile, + first(functionDeclaration.parameters), + last(functionDeclaration.parameters), + newParamDeclaration, + { joiner: ", ", + // indentation is set to 0 because otherwise the object parameter will be indented if there is a `this` parameter + indentation: 0, + leadingTriviaOption: textChanges.LeadingTriviaOption.IncludeAll, + trailingTriviaOption: textChanges.TrailingTriviaOption.Include + }); + + const functionCalls = sortAndDeduplicate(groupedReferences.functionCalls, /*comparer*/ (a, b) => compareValues(a.pos, b.pos)); + for (const call of functionCalls) { + if (call.arguments && call.arguments.length) { + const newArgument = getSynthesizedDeepClone(createNewArgument(functionDeclaration, call.arguments), /*includeTrivia*/ true); + changes.replaceNodeRange( + getSourceFileOfNode(call), + first(call.arguments), + last(call.arguments), + newArgument, + { leadingTriviaOption: textChanges.LeadingTriviaOption.IncludeAll, trailingTriviaOption: textChanges.TrailingTriviaOption.Include }); + } + } + } + + function getGroupedReferences(functionDeclaration: ValidFunctionDeclaration, program: Program, cancellationToken: CancellationToken): GroupedReferences { + const functionNames = getFunctionNames(functionDeclaration); + const classNames = isConstructorDeclaration(functionDeclaration) ? getClassNames(functionDeclaration) : []; + const names = deduplicate([...functionNames, ...classNames], equateValues); + const checker = program.getTypeChecker(); + + const references = flatMap(names, /*mapfn*/ name => FindAllReferences.getReferenceEntriesForNode(-1, name, program, program.getSourceFiles(), cancellationToken)); + const groupedReferences = groupReferences(references); + + if (!every(groupedReferences.declarations, decl => contains(names, decl))) { + groupedReferences.valid = false; + } + + return groupedReferences; + + function groupReferences(referenceEntries: ReadonlyArray): GroupedReferences { + const classReferences: ClassReferences = { accessExpressions: [], typeUsages: [] }; + const groupedReferences: GroupedReferences = { functionCalls: [], declarations: [], classReferences, valid: true }; + const functionSymbols = map(functionNames, checker.getSymbolAtLocation); + const classSymbols = map(classNames, checker.getSymbolAtLocation); + const isConstructor = isConstructorDeclaration(functionDeclaration); + + for (const entry of referenceEntries) { + if (entry.kind !== FindAllReferences.EntryKind.Node) { + groupedReferences.valid = false; + continue; + } + if (contains(functionSymbols, checker.getSymbolAtLocation(entry.node), symbolComparer)) { + const decl = entryToDeclaration(entry); + if (decl) { + groupedReferences.declarations.push(decl); + continue; + } + + const call = entryToFunctionCall(entry); + if (call) { + groupedReferences.functionCalls.push(call); + continue; + } + } + // if the refactored function is a constructor, we must also check if the references to its class are valid + if (isConstructor && contains(classSymbols, checker.getSymbolAtLocation(entry.node), symbolComparer)) { + const decl = entryToDeclaration(entry); + if (decl) { + groupedReferences.declarations.push(decl); + continue; + } + + const accessExpression = entryToAccessExpression(entry); + if (accessExpression) { + classReferences.accessExpressions.push(accessExpression); + continue; + } + + // Only class declarations are allowed to be used as a type (in a heritage clause), + // otherwise `findAllReferences` might not be able to track constructor calls. + if (isClassDeclaration(functionDeclaration.parent)) { + const type = entryToType(entry); + if (type) { + classReferences.typeUsages.push(type); + continue; + } + } + } + groupedReferences.valid = false; + } + + return groupedReferences; + } + } + + function symbolComparer(a: Symbol, b: Symbol): boolean { + return getSymbolTarget(a) === getSymbolTarget(b); + } + + function entryToDeclaration(entry: FindAllReferences.NodeEntry): Node | undefined { + if (isDeclaration(entry.node.parent)) { + return entry.node; + } + return undefined; + } + + function entryToFunctionCall(entry: FindAllReferences.NodeEntry): CallExpression | NewExpression | undefined { + if (entry.node.parent) { + const functionReference = entry.node; + const parent = functionReference.parent; + switch (parent.kind) { + // Function call (foo(...) or super(...)) + case SyntaxKind.CallExpression: + const callExpression = tryCast(parent, isCallExpression); + if (callExpression && callExpression.expression === functionReference) { + return callExpression; + } + break; + // Constructor call (new Foo(...)) + case SyntaxKind.NewExpression: + const newExpression = tryCast(parent, isNewExpression); + if (newExpression && newExpression.expression === functionReference) { + return newExpression; + } + break; + // Method call (x.foo(...)) + case SyntaxKind.PropertyAccessExpression: + const propertyAccessExpression = tryCast(parent, isPropertyAccessExpression); + if (propertyAccessExpression && propertyAccessExpression.parent && propertyAccessExpression.name === functionReference) { + const callExpression = tryCast(propertyAccessExpression.parent, isCallExpression); + if (callExpression && callExpression.expression === propertyAccessExpression) { + return callExpression; + } + } + break; + // Method call (x["foo"](...)) + case SyntaxKind.ElementAccessExpression: + const elementAccessExpression = tryCast(parent, isElementAccessExpression); + if (elementAccessExpression && elementAccessExpression.parent && elementAccessExpression.argumentExpression === functionReference) { + const callExpression = tryCast(elementAccessExpression.parent, isCallExpression); + if (callExpression && callExpression.expression === elementAccessExpression) { + return callExpression; + } + } + break; + } + } + return undefined; + } + + function entryToAccessExpression(entry: FindAllReferences.NodeEntry): ElementAccessExpression | PropertyAccessExpression | undefined { + if (entry.node.parent) { + const reference = entry.node; + const parent = reference.parent; + switch (parent.kind) { + // `C.foo` + case SyntaxKind.PropertyAccessExpression: + const propertyAccessExpression = tryCast(parent, isPropertyAccessExpression); + if (propertyAccessExpression && propertyAccessExpression.expression === reference) { + return propertyAccessExpression; + } + break; + // `C["foo"]` + case SyntaxKind.ElementAccessExpression: + const elementAccessExpression = tryCast(parent, isElementAccessExpression); + if (elementAccessExpression && elementAccessExpression.expression === reference) { + return elementAccessExpression; + } + break; + } + } + return undefined; + } + + function entryToType(entry: FindAllReferences.NodeEntry): Node | undefined { + const reference = entry.node; + if (getMeaningFromLocation(reference) === SemanticMeaning.Type || isExpressionWithTypeArgumentsInClassExtendsClause(reference.parent)) { + return reference; + } + return undefined; + } + + function getFunctionDeclarationAtPosition(file: SourceFile, startPosition: number, checker: TypeChecker): ValidFunctionDeclaration | undefined { + const node = getTouchingToken(file, startPosition); + const functionDeclaration = getContainingFunction(node); + if (functionDeclaration + && isValidFunctionDeclaration(functionDeclaration, checker) + && rangeContainsRange(functionDeclaration, node) + && !(functionDeclaration.body && rangeContainsRange(functionDeclaration.body, node))) return functionDeclaration; + + return undefined; + } + + function isValidFunctionDeclaration(functionDeclaration: SignatureDeclaration, checker: TypeChecker): functionDeclaration is ValidFunctionDeclaration { + if (!isValidParameterNodeArray(functionDeclaration.parameters)) return false; + switch (functionDeclaration.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.MethodDeclaration: + return !!functionDeclaration.name && !!functionDeclaration.body && !checker.isImplementationOfOverload(functionDeclaration); + case SyntaxKind.Constructor: + if (isClassDeclaration(functionDeclaration.parent)) { + return !!functionDeclaration.body && !!functionDeclaration.parent.name && !checker.isImplementationOfOverload(functionDeclaration); + } + else { + return isValidVariableDeclaration(functionDeclaration.parent.parent) && !!functionDeclaration.body && !checker.isImplementationOfOverload(functionDeclaration); + } + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + return isValidVariableDeclaration(functionDeclaration.parent); + } + return false; + } + + function isValidParameterNodeArray(parameters: NodeArray): parameters is ValidParameterNodeArray { + return getRefactorableParametersLength(parameters) >= minimumParameterLength && every(parameters, isValidParameterDeclaration); + } + + function isValidParameterDeclaration(paramDeclaration: ParameterDeclaration): paramDeclaration is ValidParameterDeclaration { + return !paramDeclaration.modifiers && !paramDeclaration.decorators && isIdentifier(paramDeclaration.name); + } + + function isValidVariableDeclaration(node: Node): node is ValidVariableDeclaration { + return isVariableDeclaration(node) && isVarConst(node) && isIdentifier(node.name) && !node.type; // TODO: GH#30113 + } + + function hasThisParameter(parameters: NodeArray): boolean { + return parameters.length > 0 && isThis(parameters[0].name); + } + + function getRefactorableParametersLength(parameters: NodeArray): number { + if (hasThisParameter(parameters)) { + return parameters.length - 1; + } + return parameters.length; + } + + function getRefactorableParameters(parameters: NodeArray): NodeArray { + if (hasThisParameter(parameters)) { + parameters = createNodeArray(parameters.slice(1), parameters.hasTrailingComma); + } + return parameters; + } + + function createNewArgument(functionDeclaration: ValidFunctionDeclaration, functionArguments: NodeArray): ObjectLiteralExpression { + const parameters = getRefactorableParameters(functionDeclaration.parameters); + const hasRestParameter = isRestParameter(last(parameters)); + const nonRestArguments = hasRestParameter ? functionArguments.slice(0, parameters.length - 1) : functionArguments; + const properties = map(nonRestArguments, (arg, i) => { + const property = createPropertyAssignment(getParameterName(parameters[i]), arg); + suppressLeadingAndTrailingTrivia(property.initializer); + copyComments(arg, property); + return property; + }); + + if (hasRestParameter && functionArguments.length >= parameters.length) { + const restArguments = functionArguments.slice(parameters.length - 1); + const restProperty = createPropertyAssignment(getParameterName(last(parameters)), createArrayLiteral(restArguments)); + properties.push(restProperty); + } + + const objectLiteral = createObjectLiteral(properties, /*multiLine*/ false); + return objectLiteral; + } + + function createNewParameters(functionDeclaration: ValidFunctionDeclaration, program: Program, host: LanguageServiceHost): NodeArray { + const refactorableParameters = getRefactorableParameters(functionDeclaration.parameters); + const bindingElements = map(refactorableParameters, createBindingElementFromParameterDeclaration); + const objectParameterName = createObjectBindingPattern(bindingElements); + const objectParameterType = createParameterTypeNode(refactorableParameters); + const checker = program.getTypeChecker(); + + let objectInitializer: Expression | undefined; + // If every parameter in the original function was optional, add an empty object initializer to the new object parameter + if (every(refactorableParameters, checker.isOptionalParameter)) { + objectInitializer = createObjectLiteral(); + } + + const objectParameter = createParameter( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + objectParameterName, + /*questionToken*/ undefined, + objectParameterType, + objectInitializer); + + if (hasThisParameter(functionDeclaration.parameters)) { + const thisParameter = functionDeclaration.parameters[0]; + const newThisParameter = createParameter( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + thisParameter.name, + /*questionToken*/ undefined, + thisParameter.type); + + suppressLeadingAndTrailingTrivia(newThisParameter.name); + copyComments(thisParameter.name, newThisParameter.name); + if (thisParameter.type) { + suppressLeadingAndTrailingTrivia(newThisParameter.type!); + copyComments(thisParameter.type, newThisParameter.type!); + } + + return createNodeArray([newThisParameter, objectParameter]); + } + return createNodeArray([objectParameter]); + + function createParameterTypeNode(parameters: NodeArray): TypeLiteralNode { + const members = map(parameters, createPropertySignatureFromParameterDeclaration); + const typeNode = addEmitFlags(createTypeLiteralNode(members), EmitFlags.SingleLine); + return typeNode; + } + + function createPropertySignatureFromParameterDeclaration(parameterDeclaration: ValidParameterDeclaration): PropertySignature { + let parameterType = parameterDeclaration.type; + if (!parameterType && (parameterDeclaration.initializer || isRestParameter(parameterDeclaration))) { + parameterType = getTypeNode(parameterDeclaration); + } + + const propertySignature = createPropertySignature( + /*modifiers*/ undefined, + getParameterName(parameterDeclaration), + parameterDeclaration.initializer || isRestParameter(parameterDeclaration) ? createToken(SyntaxKind.QuestionToken) : parameterDeclaration.questionToken, + parameterType, + /*initializer*/ undefined); + + suppressLeadingAndTrailingTrivia(propertySignature); + copyComments(parameterDeclaration.name, propertySignature.name); + if (parameterDeclaration.type && propertySignature.type) { + copyComments(parameterDeclaration.type, propertySignature.type); + } + + return propertySignature; + } + + function getTypeNode(node: Node): TypeNode | undefined { + const checker = program.getTypeChecker(); + const type = checker.getTypeAtLocation(node); + return getTypeNodeIfAccessible(type, node, program, host); + } + } + + function createBindingElementFromParameterDeclaration(parameterDeclaration: ValidParameterDeclaration): BindingElement { + const element = createBindingElement( + /*dotDotDotToken*/ undefined, + /*propertyName*/ undefined, + getParameterName(parameterDeclaration), + isRestParameter(parameterDeclaration) ? createArrayLiteral() : parameterDeclaration.initializer); + + suppressLeadingAndTrailingTrivia(element); + if (parameterDeclaration.initializer && element.initializer) { + copyComments(parameterDeclaration.initializer, element.initializer); + } + return element; + } + + function copyComments(sourceNode: Node, targetNode: Node) { + const sourceFile = sourceNode.getSourceFile(); + const text = sourceFile.text; + if (hasLeadingLineBreak(sourceNode, text)) { + copyLeadingComments(sourceNode, targetNode, sourceFile); + } + else { + copyTrailingAsLeadingComments(sourceNode, targetNode, sourceFile); + } + copyTrailingComments(sourceNode, targetNode, sourceFile); + } + + function hasLeadingLineBreak(node: Node, text: string) { + const start = node.getFullStart(); + const end = node.getStart(); + for (let i = start; i < end; i++) { + if (text.charCodeAt(i) === CharacterCodes.lineFeed) return true; + } + return false; + } + + function getParameterName(paramDeclaration: ValidParameterDeclaration) { + return getTextOfIdentifierOrLiteral(paramDeclaration.name); + } + + function getClassNames(constructorDeclaration: ValidConstructor): Identifier[] { + switch (constructorDeclaration.parent.kind) { + case SyntaxKind.ClassDeclaration: + const classDeclaration = constructorDeclaration.parent; + return [classDeclaration.name]; + case SyntaxKind.ClassExpression: + const classExpression = constructorDeclaration.parent; + const variableDeclaration = constructorDeclaration.parent.parent; + const className = classExpression.name; + if (className) return [className, variableDeclaration.name]; + return [variableDeclaration.name]; + } + } + + function getFunctionNames(functionDeclaration: ValidFunctionDeclaration): Node[] { + switch (functionDeclaration.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.MethodDeclaration: + return [functionDeclaration.name]; + case SyntaxKind.Constructor: + const ctrKeyword = findChildOfKind(functionDeclaration, SyntaxKind.ConstructorKeyword, functionDeclaration.getSourceFile())!; + if (functionDeclaration.parent.kind === SyntaxKind.ClassExpression) { + const variableDeclaration = functionDeclaration.parent.parent; + return [variableDeclaration.name, ctrKeyword]; + } + return [ctrKeyword]; + case SyntaxKind.ArrowFunction: + return [functionDeclaration.parent.name]; + case SyntaxKind.FunctionExpression: + if (functionDeclaration.name) return [functionDeclaration.name, functionDeclaration.parent.name]; + return [functionDeclaration.parent.name]; + default: + return Debug.assertNever(functionDeclaration); + } + } + + type ValidParameterNodeArray = NodeArray; + + interface ValidVariableDeclaration extends VariableDeclaration { + name: Identifier; + type: undefined; + } + + interface ValidConstructor extends ConstructorDeclaration { + parent: (ClassDeclaration & { name: Identifier }) | (ClassExpression & { parent: ValidVariableDeclaration }); + parameters: NodeArray; + body: FunctionBody; + } + + interface ValidFunction extends FunctionDeclaration { + name: Identifier; + parameters: NodeArray; + body: FunctionBody; + } + + interface ValidMethod extends MethodDeclaration { + parameters: NodeArray; + body: FunctionBody; + } + + interface ValidFunctionExpression extends FunctionExpression { + parent: ValidVariableDeclaration; + parameters: NodeArray; + } + + interface ValidArrowFunction extends ArrowFunction { + parent: ValidVariableDeclaration; + parameters: NodeArray; + } + + type ValidFunctionDeclaration = ValidConstructor | ValidFunction | ValidMethod | ValidArrowFunction | ValidFunctionExpression; + + interface ValidParameterDeclaration extends ParameterDeclaration { + name: Identifier; + modifiers: undefined; + decorators: undefined; + } + + interface GroupedReferences { + functionCalls: (CallExpression | NewExpression)[]; + declarations: Node[]; + classReferences?: ClassReferences; + valid: boolean; + } + interface ClassReferences { + accessExpressions: Node[]; + typeUsages: Node[]; + } +} \ No newline at end of file diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 4689e18508c8e..8144162763533 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -28,17 +28,27 @@ namespace ts.textChanges { } export interface ConfigurableStart { - /** True to use getStart() (NB, not getFullStart()) without adjustment. */ - useNonAdjustedStartPosition?: boolean; + leadingTriviaOption?: LeadingTriviaOption; } export interface ConfigurableEnd { - /** True to use getEnd() without adjustment. */ - useNonAdjustedEndPosition?: boolean; + trailingTriviaOption?: TrailingTriviaOption; } - export enum Position { - FullStart, - Start + export enum LeadingTriviaOption { + /** Exclude all leading trivia (use getStart()) */ + Exclude, + /** Include leading trivia and, + * if there are no line breaks between the node and the previous token, + * include all trivia between the node and the previous token + */ + IncludeAll, + } + + export enum TrailingTriviaOption { + /** Exclude all trailing trivia (use getEnd()) */ + Exclude, + /** Include trailing trivia */ + Include, } function skipWhitespacesAndLineBreaks(text: string, start: number) { @@ -68,13 +78,14 @@ namespace ts.textChanges { * Usually leading trivia of the variable declaration 'y' should not include trailing trivia (whitespace, comment 'this is x' and newline) from the preceding * variable declaration and trailing trivia for 'y' should include (whitespace, comment 'this is y', newline). * By default when removing nodes we adjust start and end positions to respect specification of the trivia above. - * If pos\end should be interpreted literally 'useNonAdjustedStartPosition' or 'useNonAdjustedEndPosition' should be set to true + * If pos\end should be interpreted literally (that is, withouth including leading and trailing trivia), `leadingTriviaOption` should be set to `LeadingTriviaOption.Exclude` + * and `trailingTriviaOption` to `TrailingTriviaOption.Exclude`. */ export interface ConfigurableStartEnd extends ConfigurableStart, ConfigurableEnd {} - export const useNonAdjustedPositions: ConfigurableStartEnd = { - useNonAdjustedStartPosition: true, - useNonAdjustedEndPosition: true, + const useNonAdjustedPositions: ConfigurableStartEnd = { + leadingTriviaOption: LeadingTriviaOption.Exclude, + trailingTriviaOption: TrailingTriviaOption.Exclude, }; export interface InsertNodeOptions { @@ -143,11 +154,12 @@ namespace ts.textChanges { } function getAdjustedRange(sourceFile: SourceFile, startNode: Node, endNode: Node, options: ConfigurableStartEnd): TextRange { - return { pos: getAdjustedStartPosition(sourceFile, startNode, options, Position.Start), end: getAdjustedEndPosition(sourceFile, endNode, options) }; + return { pos: getAdjustedStartPosition(sourceFile, startNode, options), end: getAdjustedEndPosition(sourceFile, endNode, options) }; } - function getAdjustedStartPosition(sourceFile: SourceFile, node: Node, options: ConfigurableStart, position: Position) { - if (options.useNonAdjustedStartPosition) { + function getAdjustedStartPosition(sourceFile: SourceFile, node: Node, options: ConfigurableStart) { + const { leadingTriviaOption } = options; + if (leadingTriviaOption === LeadingTriviaOption.Exclude) { return node.getStart(sourceFile); } const fullStart = node.getFullStart(); @@ -165,7 +177,7 @@ namespace ts.textChanges { // fullstart // when b is replaced - we usually want to keep the leading trvia // when b is deleted - we delete it - return position === Position.Start ? start : fullStart; + return leadingTriviaOption === LeadingTriviaOption.IncludeAll ? fullStart : start; } // get start position of the line following the line that contains fullstart position // (but only if the fullstart isn't the very beginning of the file) @@ -178,11 +190,12 @@ namespace ts.textChanges { function getAdjustedEndPosition(sourceFile: SourceFile, node: Node, options: ConfigurableEnd) { const { end } = node; - if (options.useNonAdjustedEndPosition || isExpression(node)) { + const { trailingTriviaOption } = options; + if (trailingTriviaOption === TrailingTriviaOption.Exclude || (isExpression(node) && trailingTriviaOption !== TrailingTriviaOption.Include)) { return end; } const newEnd = skipTrivia(sourceFile.text, end, /*stopAfterLineBreak*/ true); - return newEnd !== end && isLineBreak(sourceFile.text.charCodeAt(newEnd - 1)) + return newEnd !== end && (trailingTriviaOption === TrailingTriviaOption.Include || isLineBreak(sourceFile.text.charCodeAt(newEnd - 1))) ? newEnd : end; } @@ -240,15 +253,15 @@ namespace ts.textChanges { this.deleteRange(sourceFile, { pos: modifier.getStart(sourceFile), end: skipTrivia(sourceFile.text, modifier.end, /*stopAfterLineBreak*/ true) }); } - public deleteNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, options: ConfigurableStartEnd = {}): void { - const startPosition = getAdjustedStartPosition(sourceFile, startNode, options, Position.FullStart); + public deleteNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, options: ConfigurableStartEnd = { leadingTriviaOption: LeadingTriviaOption.IncludeAll }): void { + const startPosition = getAdjustedStartPosition(sourceFile, startNode, options); const endPosition = getAdjustedEndPosition(sourceFile, endNode, options); this.deleteRange(sourceFile, { pos: startPosition, end: endPosition }); } - public deleteNodeRangeExcludingEnd(sourceFile: SourceFile, startNode: Node, afterEndNode: Node | undefined, options: ConfigurableStartEnd = {}): void { - const startPosition = getAdjustedStartPosition(sourceFile, startNode, options, Position.FullStart); - const endPosition = afterEndNode === undefined ? sourceFile.text.length : getAdjustedStartPosition(sourceFile, afterEndNode, options, Position.FullStart); + public deleteNodeRangeExcludingEnd(sourceFile: SourceFile, startNode: Node, afterEndNode: Node | undefined, options: ConfigurableStartEnd = { leadingTriviaOption: LeadingTriviaOption.IncludeAll }): void { + const startPosition = getAdjustedStartPosition(sourceFile, startNode, options); + const endPosition = afterEndNode === undefined ? sourceFile.text.length : getAdjustedStartPosition(sourceFile, afterEndNode, options); this.deleteRange(sourceFile, { pos: startPosition, end: endPosition }); } @@ -307,7 +320,7 @@ namespace ts.textChanges { } public insertNodeBefore(sourceFile: SourceFile, before: Node, newNode: Node, blankLineBetween = false): void { - this.insertNodeAt(sourceFile, getAdjustedStartPosition(sourceFile, before, {}, Position.Start), newNode, this.getOptionsForInsertNodeBefore(before, blankLineBetween)); + this.insertNodeAt(sourceFile, getAdjustedStartPosition(sourceFile, before, {}), newNode, this.getOptionsForInsertNodeBefore(before, blankLineBetween)); } public insertModifierBefore(sourceFile: SourceFile, modifier: SyntaxKind, before: Node): void { @@ -427,7 +440,7 @@ namespace ts.textChanges { } public insertNodeAtEndOfScope(sourceFile: SourceFile, scope: Node, newNode: Node): void { - const pos = getAdjustedStartPosition(sourceFile, scope.getLastToken()!, {}, Position.Start); + const pos = getAdjustedStartPosition(sourceFile, scope.getLastToken()!, {}); this.insertNodeAt(sourceFile, pos, newNode, { prefix: isLineBreak(sourceFile.text.charCodeAt(scope.getLastToken()!.pos)) ? this.newLineCharacter : this.newLineCharacter + this.newLineCharacter, suffix: this.newLineCharacter @@ -736,7 +749,7 @@ namespace ts.textChanges { // find first non-whitespace position in the leading trivia of the node function startPositionToDeleteNodeInList(sourceFile: SourceFile, node: Node): number { - return skipTrivia(sourceFile.text, getAdjustedStartPosition(sourceFile, node, {}, Position.FullStart), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true); + return skipTrivia(sourceFile.text, getAdjustedStartPosition(sourceFile, node, { leadingTriviaOption: LeadingTriviaOption.IncludeAll }), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true); } function getClassOrObjectBraceEnds(cls: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression, sourceFile: SourceFile): [number, number] { @@ -1090,7 +1103,7 @@ namespace ts.textChanges { case SyntaxKind.ImportDeclaration: deleteNode(changes, sourceFile, node, // For first import, leave header comment in place - node === sourceFile.imports[0].parent ? { useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: false } : undefined); + node === sourceFile.imports[0].parent ? { leadingTriviaOption: LeadingTriviaOption.Exclude } : undefined); break; case SyntaxKind.BindingElement: @@ -1134,7 +1147,7 @@ namespace ts.textChanges { deleteNodeInList(changes, deletedNodesInLists, sourceFile, node); } else { - deleteNode(changes, sourceFile, node, node.kind === SyntaxKind.SemicolonToken ? { useNonAdjustedEndPosition: true } : undefined); + deleteNode(changes, sourceFile, node, node.kind === SyntaxKind.SemicolonToken ? { trailingTriviaOption: TrailingTriviaOption.Exclude } : undefined); } } } @@ -1213,8 +1226,8 @@ namespace ts.textChanges { /** Warning: This deletes comments too. See `copyComments` in `convertFunctionToEs6Class`. */ // Exported for tests only! (TODO: improve tests to not need this) - export function deleteNode(changes: ChangeTracker, sourceFile: SourceFile, node: Node, options: ConfigurableStartEnd = {}): void { - const startPosition = getAdjustedStartPosition(sourceFile, node, options, Position.FullStart); + export function deleteNode(changes: ChangeTracker, sourceFile: SourceFile, node: Node, options: ConfigurableStartEnd = { leadingTriviaOption: LeadingTriviaOption.IncludeAll }): void { + const startPosition = getAdjustedStartPosition(sourceFile, node, options); const endPosition = getAdjustedEndPosition(sourceFile, node, options); changes.deleteRange(sourceFile, { pos: startPosition, end: endPosition }); } diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index 21be663055ae4..793089e62c829 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -84,6 +84,7 @@ "refactors/generateGetAccessorAndSetAccessor.ts", "refactors/moveToNewFile.ts", "refactors/addOrRemoveBracesToArrowFunction.ts", + "refactors/convertToNamedParameters.ts", "services.ts", "breakpoints.ts", "transform.ts", diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 285ca0cc6a986..004135a526e50 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1664,6 +1664,18 @@ namespace ts { return ensureScriptKind(fileName, host && host.getScriptKind && host.getScriptKind(fileName)); } + export function getSymbolTarget(symbol: Symbol): Symbol { + let next: Symbol = symbol; + while (isTransientSymbol(next) && next.target) { + next = next.target; + } + return next; + } + + function isTransientSymbol(symbol: Symbol): symbol is TransientSymbol { + return (symbol.flags & SymbolFlags.Transient) !== 0; + } + export function getUniqueSymbolId(symbol: Symbol, checker: TypeChecker) { return getSymbolId(skipAlias(symbol, checker)); } @@ -1821,8 +1833,28 @@ namespace ts { return lastPos; } - export function copyComments(sourceNode: Node, targetNode: Node, sourceFile: SourceFile, commentKind?: CommentKind, hasTrailingNewLine?: boolean) { - forEachLeadingCommentRange(sourceFile.text, sourceNode.pos, (pos, end, kind, htnl) => { + export function copyLeadingComments(sourceNode: Node, targetNode: Node, sourceFile: SourceFile, commentKind?: CommentKind, hasTrailingNewLine?: boolean) { + forEachLeadingCommentRange(sourceFile.text, sourceNode.pos, getAddCommentsFunction(targetNode, sourceFile, commentKind, hasTrailingNewLine, addSyntheticLeadingComment)); + } + + + export function copyTrailingComments(sourceNode: Node, targetNode: Node, sourceFile: SourceFile, commentKind?: CommentKind, hasTrailingNewLine?: boolean) { + forEachTrailingCommentRange(sourceFile.text, sourceNode.end, getAddCommentsFunction(targetNode, sourceFile, commentKind, hasTrailingNewLine, addSyntheticTrailingComment)); + } + + /** + * This function copies the trailing comments for the token that comes before `sourceNode`, as leading comments of `targetNode`. + * This is useful because sometimes a comment that refers to `sourceNode` will be a leading comment for `sourceNode`, according to the + * notion of trivia ownership, and instead will be a trailing comment for the token before `sourceNode`, e.g.: + * `function foo(\* not leading comment for a *\ a: string) {}` + * The comment refers to `a` but belongs to the `(` token, but we might want to copy it. + */ + export function copyTrailingAsLeadingComments(sourceNode: Node, targetNode: Node, sourceFile: SourceFile, commentKind?: CommentKind, hasTrailingNewLine?: boolean) { + forEachTrailingCommentRange(sourceFile.text, sourceNode.pos, getAddCommentsFunction(targetNode, sourceFile, commentKind, hasTrailingNewLine, addSyntheticLeadingComment)); + } + + function getAddCommentsFunction(targetNode: Node, sourceFile: SourceFile, commentKind: CommentKind | undefined, hasTrailingNewLine: boolean | undefined, cb: (node: Node, kind: CommentKind, text: string, hasTrailingNewLine?: boolean) => void) { + return (pos: number, end: number, kind: CommentKind, htnl: boolean) => { if (kind === SyntaxKind.MultiLineCommentTrivia) { // Remove leading /* pos += 2; @@ -1833,8 +1865,8 @@ namespace ts { // Remove leading // pos += 2; } - addSyntheticLeadingComment(targetNode, commentKind || kind, sourceFile.text.slice(pos, end), hasTrailingNewLine !== undefined ? hasTrailingNewLine : htnl); - }); + cb(targetNode, commentKind || kind, sourceFile.text.slice(pos, end), hasTrailingNewLine !== undefined ? hasTrailingNewLine : htnl); + }; } function indexInTextChange(change: string, name: string): number { @@ -1914,4 +1946,28 @@ namespace ts { export function getSwitchedType(caseClause: CaseClause, checker: TypeChecker): Type | undefined { return checker.getTypeAtLocation(caseClause.parent.parent.expression); } + + export function getTypeNodeIfAccessible(type: Type, enclosingScope: Node, program: Program, host: LanguageServiceHost): TypeNode | undefined { + const checker = program.getTypeChecker(); + let typeIsAccessible = true; + const notAccessible = () => { typeIsAccessible = false; }; + const res = checker.typeToTypeNode(type, enclosingScope, /*flags*/ undefined, { + trackSymbol: (symbol, declaration, meaning) => { + // TODO: GH#18217 + typeIsAccessible = typeIsAccessible && checker.isSymbolAccessible(symbol, declaration, meaning!, /*shouldComputeAliasToMarkVisible*/ false).accessibility === SymbolAccessibility.Accessible; + }, + reportInaccessibleThisError: notAccessible, + reportPrivateInBaseOfClassExpression: notAccessible, + reportInaccessibleUniqueSymbolError: notAccessible, + moduleResolverHost: { + readFile: host.readFile, + fileExists: host.fileExists, + directoryExists: host.directoryExists, + getSourceFiles: program.getSourceFiles, + getCurrentDirectory: program.getCurrentDirectory, + getCommonSourceDirectory: program.getCommonSourceDirectory, + } + }); + return typeIsAccessible ? res : undefined; + } } diff --git a/src/testRunner/unittests/services/textChanges.ts b/src/testRunner/unittests/services/textChanges.ts index de92efafbeb4b..27195c300348a 100644 --- a/src/testRunner/unittests/services/textChanges.ts +++ b/src/testRunner/unittests/services/textChanges.ts @@ -140,13 +140,13 @@ var z = 3; // comment 4 deleteNode(changeTracker, sourceFile, findVariableStatementContaining("y", sourceFile)); }); runSingleFileTest("deleteNode2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - deleteNode(changeTracker, sourceFile, findVariableStatementContaining("y", sourceFile), { useNonAdjustedStartPosition: true }); + deleteNode(changeTracker, sourceFile, findVariableStatementContaining("y", sourceFile), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude }); }); runSingleFileTest("deleteNode3", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - deleteNode(changeTracker, sourceFile, findVariableStatementContaining("y", sourceFile), { useNonAdjustedEndPosition: true }); + deleteNode(changeTracker, sourceFile, findVariableStatementContaining("y", sourceFile), { trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude }); }); runSingleFileTest("deleteNode4", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { - deleteNode(changeTracker, sourceFile, findVariableStatementContaining("y", sourceFile), { useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: true }); + deleteNode(changeTracker, sourceFile, findVariableStatementContaining("y", sourceFile), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude }); }); runSingleFileTest("deleteNode5", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { deleteNode(changeTracker, sourceFile, findVariableStatementContaining("x", sourceFile)); @@ -167,15 +167,15 @@ var a = 4; // comment 7 }); runSingleFileTest("deleteNodeRange2", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), - { useNonAdjustedStartPosition: true }); + { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude }); }); runSingleFileTest("deleteNodeRange3", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), - { useNonAdjustedEndPosition: true }); + { trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude }); }); runSingleFileTest("deleteNodeRange4", /*placeOpenBraceOnNewLineForFunctions*/ false, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), - { useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: true }); + { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude }); }); } function createTestVariableDeclaration(name: string) { @@ -254,16 +254,16 @@ var a = 4; // comment 7`; changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { suffix: newLineCharacter }); }); runSingleFileTest("replaceNode2", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, suffix: newLineCharacter, prefix: newLineCharacter }); + changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, suffix: newLineCharacter, prefix: newLineCharacter }); }); runSingleFileTest("replaceNode3", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { useNonAdjustedEndPosition: true, suffix: newLineCharacter }); + changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude, suffix: newLineCharacter }); }); runSingleFileTest("replaceNode4", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: true }); + changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude }); }); runSingleFileTest("replaceNode5", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceNode(sourceFile, findVariableStatementContaining("x", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: true }); + changeTracker.replaceNode(sourceFile, findVariableStatementContaining("x", sourceFile), createTestClass(), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude }); }); } { @@ -279,13 +279,13 @@ var a = 4; // comment 7`; changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { suffix: newLineCharacter }); }); runSingleFileTest("replaceNodeRange2", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, suffix: newLineCharacter, prefix: newLineCharacter }); + changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, suffix: newLineCharacter, prefix: newLineCharacter }); }); runSingleFileTest("replaceNodeRange3", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { useNonAdjustedEndPosition: true, suffix: newLineCharacter }); + changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude, suffix: newLineCharacter }); }); runSingleFileTest("replaceNodeRange4", /*placeOpenBraceOnNewLineForFunctions*/ true, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: true }); + changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.Exclude }); }); } { diff --git a/tests/cases/fourslash/refactorConvertToNamedParameters_allParamsOptional.ts b/tests/cases/fourslash/refactorConvertToNamedParameters_allParamsOptional.ts new file mode 100644 index 0000000000000..825b34c303d96 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToNamedParameters_allParamsOptional.ts @@ -0,0 +1,17 @@ +/// + +////function f(/*a*/a?: number, b: string = "1"/*b*/): string { +//// return b; +////} +////f(); + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Convert to named parameters", + actionName: "Convert to named parameters", + actionDescription: "Convert to named parameters", + newContent: `function f({ a, b = "1" }: { a?: number; b?: string; } = {}): string { + return b; +} +f();` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/refactorConvertToNamedParameters_arrowFunction.ts b/tests/cases/fourslash/refactorConvertToNamedParameters_arrowFunction.ts new file mode 100644 index 0000000000000..6a5a8e328a534 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToNamedParameters_arrowFunction.ts @@ -0,0 +1,13 @@ +/// + +////const foo = /*a*/(a: number, b: number)/*b*/ => { }; +////foo(1, 2); + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Convert to named parameters", + actionName: "Convert to named parameters", + actionDescription: "Convert to named parameters", + newContent: `const foo = ({ a, b }: { a: number; b: number; }) => { }; +foo({ a: 1, b: 2 });` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/refactorConvertToNamedParameters_arrowFunctionWithContextualType.ts b/tests/cases/fourslash/refactorConvertToNamedParameters_arrowFunctionWithContextualType.ts new file mode 100644 index 0000000000000..05a0f6f54fa21 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToNamedParameters_arrowFunctionWithContextualType.ts @@ -0,0 +1,7 @@ +/// + +////const foo: (a: number, b: number) => number = /*a*/(a: number, b: number)/*b*/ => a + b; +////foo(1, 2); + +goTo.select("a", "b"); +verify.not.refactorAvailable("Convert to named parameters"); diff --git a/tests/cases/fourslash/refactorConvertToNamedParameters_callComments.ts b/tests/cases/fourslash/refactorConvertToNamedParameters_callComments.ts new file mode 100644 index 0000000000000..6fb06fe938b75 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToNamedParameters_callComments.ts @@ -0,0 +1,17 @@ +/// + +////function /*a*/foo/*b*/(a: number, b: number, ...rest: number[]) { +//// return a + b; +////} +////foo(/**a*/ 1 /**b*/, /**c*/ 2 /**d*/, /**e*/ 3 /**f*/, /**g*/ 4 /**h*/); + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Convert to named parameters", + actionName: "Convert to named parameters", + actionDescription: "Convert to named parameters", + newContent: `function foo({ a, b, rest = [] }: { a: number; b: number; rest?: number[]; }) { + return a + b; +} +foo({ /**a*/ a: 1 /**b*/, /**c*/ b: 2 /**d*/, rest: [/**e*/ 3 /**f*/, /**g*/ 4 /**h*/] });` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/refactorConvertToNamedParameters_callComments2.ts b/tests/cases/fourslash/refactorConvertToNamedParameters_callComments2.ts new file mode 100644 index 0000000000000..96a56d7024dae --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToNamedParameters_callComments2.ts @@ -0,0 +1,34 @@ +/// + +////function /*a*/foo/*b*/(a: number, b: number, ...rest: number[]) { +//// return a + b; +////} +////foo( +//// /**a*/ +//// 1, +//// /**c*/ +//// 2, +//// /**e*/ +//// 3, +//// /**g*/ +//// 4); + +goTo.select("a", "b"); +/* The expected content is currently wrong. The new argument object has the wrong formatting. */ +edit.applyRefactor({ + refactorName: "Convert to named parameters", + actionName: "Convert to named parameters", + actionDescription: "Convert to named parameters", + newContent: `function foo({ a, b, rest = [] }: { a: number; b: number; rest?: number[]; }) { + return a + b; +} +foo( + { /**a*/ + a: 1, /**c*/ + b: 2, rest: [ + /**e*/ + 3, + /**g*/ + 4] + });` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/refactorConvertToNamedParameters_chainedCall.ts b/tests/cases/fourslash/refactorConvertToNamedParameters_chainedCall.ts new file mode 100644 index 0000000000000..0fb20ec864ddd --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToNamedParameters_chainedCall.ts @@ -0,0 +1,17 @@ +/// + +////function foo(/*a*/a: number, b: number/*b*/) { +//// return { bar: () => a + b }; +////} +////var x = foo(1, 2).bar(); + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Convert to named parameters", + actionName: "Convert to named parameters", + actionDescription: "Convert to named parameters", + newContent: `function foo({ a, b }: { a: number; b: number; }) { + return { bar: () => a + b }; +} +var x = foo({ a: 1, b: 2 }).bar();` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/refactorConvertToNamedParameters_classDeclarationAliasing.ts b/tests/cases/fourslash/refactorConvertToNamedParameters_classDeclarationAliasing.ts new file mode 100644 index 0000000000000..0914dedd57d96 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToNamedParameters_classDeclarationAliasing.ts @@ -0,0 +1,20 @@ +/// + +////class Foo { +//// /*a*/constructor/*b*/(a: number, b: number) { } +////} +////const fooAlias = Foo; +////const newFoo = new fooAlias(1, 2); + +goTo.select("a", "b"); +// Refactor should not make changes +edit.applyRefactor({ + refactorName: "Convert to named parameters", + actionName: "Convert to named parameters", + actionDescription: "Convert to named parameters", + newContent: `class Foo { + constructor(a: number, b: number) { } +} +const fooAlias = Foo; +const newFoo = new fooAlias(1, 2);` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/refactorConvertToNamedParameters_classDeclarationGoodUsages.ts b/tests/cases/fourslash/refactorConvertToNamedParameters_classDeclarationGoodUsages.ts new file mode 100644 index 0000000000000..858bccd60c164 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToNamedParameters_classDeclarationGoodUsages.ts @@ -0,0 +1,31 @@ +/// + +////class C { +//// static a: number = 2; +//// /*a*/constructor/*b*/(a: number, b: number) { } +////} +////const newC = new C(1, 2); +////const b = C.a; +////C["a"] = 3; +////let c: C; +////function f(c: C) { } +////class B extends C { } +////class A implements C { } + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Convert to named parameters", + actionName: "Convert to named parameters", + actionDescription: "Convert to named parameters", + newContent: `class C { + static a: number = 2; + constructor({ a, b }: { a: number; b: number; }) { } +} +const newC = new C({ a: 1, b: 2 }); +const b = C.a; +C["a"] = 3; +let c: C; +function f(c: C) { } +class B extends C { } +class A implements C { }` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/refactorConvertToNamedParameters_classExpression.ts b/tests/cases/fourslash/refactorConvertToNamedParameters_classExpression.ts new file mode 100644 index 0000000000000..ee4b6051756a2 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToNamedParameters_classExpression.ts @@ -0,0 +1,17 @@ +/// + +////const c = class { +//// constructor(/*a*/a: number, b = { x: 1 }/*b*/) { } +////} +////var x = new c(2); + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Convert to named parameters", + actionName: "Convert to named parameters", + actionDescription: "Convert to named parameters", + newContent: `const c = class { + constructor({ a, b = { x: 1 } }: { a: number; b?: { x: number; }; }) { } +} +var x = new c({ a: 2 });` +}); diff --git a/tests/cases/fourslash/refactorConvertToNamedParameters_classExpressionGoodUsages.ts b/tests/cases/fourslash/refactorConvertToNamedParameters_classExpressionGoodUsages.ts new file mode 100644 index 0000000000000..f8023b9898c28 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToNamedParameters_classExpressionGoodUsages.ts @@ -0,0 +1,23 @@ +/// + +////const c = class C { +//// static a: number = 2; +//// /*a*/constructor/*b*/(a: number, b: number) { } +////} +////const a = new c(0, 1); +////const b = c.a; +////c["a"] = 3; + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Convert to named parameters", + actionName: "Convert to named parameters", + actionDescription: "Convert to named parameters", + newContent: `const c = class C { + static a: number = 2; + constructor({ a, b }: { a: number; b: number; }) { } +} +const a = new c({ a: 0, b: 1 }); +const b = c.a; +c["a"] = 3;` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/refactorConvertToNamedParameters_classExpressionHeritage.ts b/tests/cases/fourslash/refactorConvertToNamedParameters_classExpressionHeritage.ts new file mode 100644 index 0000000000000..14b9639c2ade0 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToNamedParameters_classExpressionHeritage.ts @@ -0,0 +1,26 @@ +/// + +////const foo = class Foo { +//// /*a*/constructor/*b*/(a: number, b: number) { } +////} +////class Bar extends foo { +//// constructor() { +//// super(1, 2); +//// } +////} + +goTo.select("a", "b"); +// Refactor should not make changes +edit.applyRefactor({ + refactorName: "Convert to named parameters", + actionName: "Convert to named parameters", + actionDescription: "Convert to named parameters", + newContent: `const foo = class Foo { + constructor(a: number, b: number) { } +} +class Bar extends foo { + constructor() { + super(1, 2); + } +}` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/refactorConvertToNamedParameters_classTypeParameters.ts b/tests/cases/fourslash/refactorConvertToNamedParameters_classTypeParameters.ts new file mode 100644 index 0000000000000..7d62621557220 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToNamedParameters_classTypeParameters.ts @@ -0,0 +1,23 @@ +/// + +////class Foo { +//// /*a*/bar/*b*/(t: T, s: T) { +//// return s; +//// } +////} +////var foo = new Foo(); +////foo.bar("a", "b"); + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Convert to named parameters", + actionName: "Convert to named parameters", + actionDescription: "Convert to named parameters", + newContent: `class Foo { + bar({ t, s }: { t: T; s: T; }) { + return s; + } +} +var foo = new Foo(); +foo.bar({ t: "a", s: "b" });` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/refactorConvertToNamedParameters_constructor.ts b/tests/cases/fourslash/refactorConvertToNamedParameters_constructor.ts new file mode 100644 index 0000000000000..86b1f9eb610f9 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToNamedParameters_constructor.ts @@ -0,0 +1,27 @@ +/// + +////class Foo { +//// t: string; +//// s: string; +//// /*a*/constructor/*b*/(t: string, s: string) { +//// this.t = t; +//// this.s = s; +//// } +////} +////var foo = new Foo("a", "b"); + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Convert to named parameters", + actionName: "Convert to named parameters", + actionDescription: "Convert to named parameters", + newContent: `class Foo { + t: string; + s: string; + constructor({ t, s }: { t: string; s: string; }) { + this.t = t; + this.s = s; + } +} +var foo = new Foo({ t: "a", s: "b" });` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/refactorConvertToNamedParameters_defaultClass.ts b/tests/cases/fourslash/refactorConvertToNamedParameters_defaultClass.ts new file mode 100644 index 0000000000000..a056a82871b5e --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToNamedParameters_defaultClass.ts @@ -0,0 +1,8 @@ +/// + +/////export default class { +//// constructor(/*a*/a: number, b = { x: 1 }/*b*/) {} +////} + +goTo.select("a", "b"); +verify.not.refactorAvailable("Convert to named parameters"); diff --git a/tests/cases/fourslash/refactorConvertToNamedParameters_function.ts b/tests/cases/fourslash/refactorConvertToNamedParameters_function.ts new file mode 100644 index 0000000000000..0834bf288fcec --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToNamedParameters_function.ts @@ -0,0 +1,17 @@ +/// + +////function f(/*a*/a: number, b: string/*b*/): string { +//// return b; +////} +////f(4, "b"); + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Convert to named parameters", + actionName: "Convert to named parameters", + actionDescription: "Convert to named parameters", + newContent: `function f({ a, b }: { a: number; b: string; }): string { + return b; +} +f({ a: 4, b: "b" });` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/refactorConvertToNamedParameters_functionComments.ts b/tests/cases/fourslash/refactorConvertToNamedParameters_functionComments.ts new file mode 100644 index 0000000000000..20238ac81de70 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToNamedParameters_functionComments.ts @@ -0,0 +1,23 @@ +/// + +////foo(1, 2); /**a*/ +/////**b*/ function /*a*/foo/*b*/(/**this1*/ this /**this2*/: /**void1*/ void /**void2*/, /**c*/ a /**d*/: /**e*/ number /**f*/, /**g*/ b /**h*/: /**i*/ number /**j*/ = /**k*/ 1 /**l*/) { +//// // m +//// /**n*/ return a + b; // o +//// // p +////} // q +/////**r*/ foo(1); + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Convert to named parameters", + actionName: "Convert to named parameters", + actionDescription: "Convert to named parameters", + newContent: `foo({ a: 1, b: 2 }); /**a*/ +/**b*/ function foo(/**this1*/ this /**this2*/: /**void1*/ void /**void2*/, { a, b = /**k*/ 1 /**l*/ }: { /**c*/ a /**d*/: /**e*/ number /**f*/; /**g*/ b /**h*/?: /**i*/ number /**j*/; }) { + // m + /**n*/ return a + b; // o + // p +} // q +/**r*/ foo({ a: 1 });` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/refactorConvertToNamedParameters_functionComments1.ts b/tests/cases/fourslash/refactorConvertToNamedParameters_functionComments1.ts new file mode 100644 index 0000000000000..e0d34c8fe6531 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToNamedParameters_functionComments1.ts @@ -0,0 +1,15 @@ +/// + +////function /*a*/foo/*b*/(a: number /** a */, b: number /** b */) { +//// return a + b; +////} + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Convert to named parameters", + actionName: "Convert to named parameters", + actionDescription: "Convert to named parameters", + newContent: `function foo({ a, b }: { a: number /** a */; b: number /** b */; }) { + return a + b; +}` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/refactorConvertToNamedParameters_functionComments2.ts b/tests/cases/fourslash/refactorConvertToNamedParameters_functionComments2.ts new file mode 100644 index 0000000000000..923b49b41988b --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToNamedParameters_functionComments2.ts @@ -0,0 +1,26 @@ +/// + +////function /*a*/foo/*b*/(// comment +//// // a comment +//// a: number, +//// // b comment +//// b: number +////) { +//// return a + b; +////} + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Convert to named parameters", + actionName: "Convert to named parameters", + actionDescription: "Convert to named parameters", + newContent: `function foo(// comment +{ a, b }: { + // a comment + a: number; + // b comment + b: number; +}) { + return a + b; +}` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/refactorConvertToNamedParameters_functionExpression.ts b/tests/cases/fourslash/refactorConvertToNamedParameters_functionExpression.ts new file mode 100644 index 0000000000000..e8aa6a92dd831 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToNamedParameters_functionExpression.ts @@ -0,0 +1,13 @@ +/// + +////const foo = /*a*/function/*b*/(a: number, b: number) { }; +////foo(1, 2); + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Convert to named parameters", + actionName: "Convert to named parameters", + actionDescription: "Convert to named parameters", + newContent: `const foo = function({ a, b }: { a: number; b: number; }) { }; +foo({ a: 1, b: 2 });` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/refactorConvertToNamedParameters_functionTypeParameters.ts b/tests/cases/fourslash/refactorConvertToNamedParameters_functionTypeParameters.ts new file mode 100644 index 0000000000000..a1597fdeed12e --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToNamedParameters_functionTypeParameters.ts @@ -0,0 +1,17 @@ +/// + +////function foo(/*a*/t: T, s: S/*b*/) { +//// return s; +////} +////foo("a", "b"); + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Convert to named parameters", + actionName: "Convert to named parameters", + actionDescription: "Convert to named parameters", + newContent: `function foo({ t, s }: { t: T; s: S; }) { + return s; +} +foo({ t: "a", s: "b" });` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/refactorConvertToNamedParameters_inheritedConstructor.ts b/tests/cases/fourslash/refactorConvertToNamedParameters_inheritedConstructor.ts new file mode 100644 index 0000000000000..bde7839a60e07 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToNamedParameters_inheritedConstructor.ts @@ -0,0 +1,24 @@ +/// + +////class Foo { +//// /*a*/constructor/*b*/(t: string, s: string) { } +////} +////class Bar extends Foo { } +////var bar = new Bar("a", "b"); +////var foo = new Foo("c", "d"); + +goTo.select("a", "b"); +/* The expected new content is currently wrong. + `new Bar("a", "b")` should be modified by the refactor to be `new Bar({ t: "a", s: "b" })` +*/ +edit.applyRefactor({ + refactorName: "Convert to named parameters", + actionName: "Convert to named parameters", + actionDescription: "Convert to named parameters", + newContent: `class Foo { + constructor({ t, s }: { t: string; s: string; }) { } +} +class Bar extends Foo { } +var bar = new Bar("a", "b"); +var foo = new Foo({ t: "c", s: "d" });` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/refactorConvertToNamedParameters_inheritedMethod.ts b/tests/cases/fourslash/refactorConvertToNamedParameters_inheritedMethod.ts new file mode 100644 index 0000000000000..3214ea4bf53af --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToNamedParameters_inheritedMethod.ts @@ -0,0 +1,25 @@ +/// + +////class Foo { +//// /*a*/bar/*b*/(t: string, s: string): string { +//// return s + t; +//// } +////} +////class Bar extends Foo { } +////var bar = new Bar(); +////bar.bar("a", "b"); + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Convert to named parameters", + actionName: "Convert to named parameters", + actionDescription: "Convert to named parameters", + newContent: `class Foo { + bar({ t, s }: { t: string; s: string; }): string { + return s + t; + } +} +class Bar extends Foo { } +var bar = new Bar(); +bar.bar({ t: "a", s: "b" });` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/refactorConvertToNamedParameters_initializer.ts b/tests/cases/fourslash/refactorConvertToNamedParameters_initializer.ts new file mode 100644 index 0000000000000..1e63f44fc3560 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToNamedParameters_initializer.ts @@ -0,0 +1,17 @@ +/// + +////function f(/*a*/a: number, b: string = "1"/*b*/): string { +//// return b; +////} +////f(4, "b"); + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Convert to named parameters", + actionName: "Convert to named parameters", + actionDescription: "Convert to named parameters", + newContent: `function f({ a, b = "1" }: { a: number; b?: string; }): string { + return b; +} +f({ a: 4, b: "b" });` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/refactorConvertToNamedParameters_initializerInference.ts b/tests/cases/fourslash/refactorConvertToNamedParameters_initializerInference.ts new file mode 100644 index 0000000000000..b6bdb7a88c2ef --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToNamedParameters_initializerInference.ts @@ -0,0 +1,17 @@ +/// + +////function f(/*a*/a: number, b = { x: 1, z: { s: true } }/*b*/) { +//// return b; +////} +////f(2); + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Convert to named parameters", + actionName: "Convert to named parameters", + actionDescription: "Convert to named parameters", + newContent: `function f({ a, b = { x: 1, z: { s: true } } }: { a: number; b?: { x: number; z: { s: boolean; }; }; }) { + return b; +} +f({ a: 2 });` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/refactorConvertToNamedParameters_method.ts b/tests/cases/fourslash/refactorConvertToNamedParameters_method.ts new file mode 100644 index 0000000000000..d7dce948cae3e --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToNamedParameters_method.ts @@ -0,0 +1,23 @@ +/// + +////class Foo { +//// /*a*/bar/*b*/(t: string, s: string): string { +//// return s + t; +//// } +////} +////var foo = new Foo(); +////foo.bar("a", "b"); + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Convert to named parameters", + actionName: "Convert to named parameters", + actionDescription: "Convert to named parameters", + newContent: `class Foo { + bar({ t, s }: { t: string; s: string; }): string { + return s + t; + } +} +var foo = new Foo(); +foo.bar({ t: "a", s: "b" });` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/refactorConvertToNamedParameters_methodCallUnion.ts b/tests/cases/fourslash/refactorConvertToNamedParameters_methodCallUnion.ts new file mode 100644 index 0000000000000..e4cafbeff3e94 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToNamedParameters_methodCallUnion.ts @@ -0,0 +1,29 @@ +/// + +////class A { +//// /*a*/foo/*b*/(a: number, b: number) { return a + b; } +////} +////class B { +//// foo(c: number, d: number) { return c + d; } +////} +////function foo(ab: A | B) { +//// return ab.foo(1, 2); +////} + + +goTo.select("a", "b"); +// Refactor should not make changes +edit.applyRefactor({ + refactorName: "Convert to named parameters", + actionName: "Convert to named parameters", + actionDescription: "Convert to named parameters", + newContent: `class A { + foo(a: number, b: number) { return a + b; } +} +class B { + foo(c: number, d: number) { return c + d; } +} +function foo(ab: A | B) { + return ab.foo(1, 2); +}` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/refactorConvertToNamedParameters_methodCalls.ts b/tests/cases/fourslash/refactorConvertToNamedParameters_methodCalls.ts new file mode 100644 index 0000000000000..f84e0d7b02349 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToNamedParameters_methodCalls.ts @@ -0,0 +1,25 @@ +/// + +////class Foo { +//// /*a*/bar/*b*/(t: string, s: string): string { +//// return s + t; +//// } +////} +////var foo = new Foo(); +////foo['bar']("a", "b"); +////foo.bar("a", "b"); + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Convert to named parameters", + actionName: "Convert to named parameters", + actionDescription: "Convert to named parameters", + newContent: `class Foo { + bar({ t, s }: { t: string; s: string; }): string { + return s + t; + } +} +var foo = new Foo(); +foo['bar']({ t: "a", s: "b" }); +foo.bar({ t: "a", s: "b" });` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/refactorConvertToNamedParameters_methodOverrides.ts b/tests/cases/fourslash/refactorConvertToNamedParameters_methodOverrides.ts new file mode 100644 index 0000000000000..b6daac17dd494 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToNamedParameters_methodOverrides.ts @@ -0,0 +1,45 @@ +/// + +////class A { +//// /*a*/foo/*b*/(a: number, b: number) { } +////} +////class B extends A { +//// /*c*/foo/*d*/(c: number, d: number) { } +////} +////var a = new A(); +////a.foo(3, 4); +////var b = new B(); +////b.foo(5, 6); + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Convert to named parameters", + actionName: "Convert to named parameters", + actionDescription: "Convert to named parameters", + newContent: `class A { + foo(a: number, b: number) { } +} +class B extends A { + foo(c: number, d: number) { } +} +var a = new A(); +a.foo(3, 4); +var b = new B(); +b.foo(5, 6);` +}); +goTo.select("c", "d"); +edit.applyRefactor({ + refactorName: "Convert to named parameters", + actionName: "Convert to named parameters", + actionDescription: "Convert to named parameters", + newContent: `class A { + foo(a: number, b: number) { } +} +class B extends A { + foo(c: number, d: number) { } +} +var a = new A(); +a.foo(3, 4); +var b = new B(); +b.foo(5, 6);` +}); diff --git a/tests/cases/fourslash/refactorConvertToNamedParameters_overloads.ts b/tests/cases/fourslash/refactorConvertToNamedParameters_overloads.ts new file mode 100644 index 0000000000000..772f8ea884818 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToNamedParameters_overloads.ts @@ -0,0 +1,10 @@ +/// + +////function f(a: number, b: number); +////function f(/*a*/a: number, b = 1/*b*/) { +//// return b; +////} +////f(2); + +goTo.select("a", "b"); +verify.not.refactorAvailable("Convert to named parameters"); \ No newline at end of file diff --git a/tests/cases/fourslash/refactorConvertToNamedParameters_paramDecorator.ts b/tests/cases/fourslash/refactorConvertToNamedParameters_paramDecorator.ts new file mode 100644 index 0000000000000..180798af95d05 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToNamedParameters_paramDecorator.ts @@ -0,0 +1,11 @@ +/// + +////declare function required(target: Object, propertyKey: string | symbol, parameterIndex: number) +////class C { +//// /*a*/bar/*b*/(@required a: number, b: number) { +//// +//// } +////} + +goTo.select("a", "b"); +verify.not.refactorAvailable("Convert to named parameters"); \ No newline at end of file diff --git a/tests/cases/fourslash/refactorConvertToNamedParameters_recursiveFunction.ts b/tests/cases/fourslash/refactorConvertToNamedParameters_recursiveFunction.ts new file mode 100644 index 0000000000000..d1513cb7bf0c0 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToNamedParameters_recursiveFunction.ts @@ -0,0 +1,20 @@ +/// + +////const f = function foo(/*a*/a: number, b: number/*b*/) { +//// foo(1, 2); +////} +////function foo(a: number, b: number) { } +////foo(3, 4); + + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Convert to named parameters", + actionName: "Convert to named parameters", + actionDescription: "Convert to named parameters", + newContent: `const f = function foo({ a, b }: { a: number; b: number; }) { + foo({ a: 1, b: 2 }); +} +function foo(a: number, b: number) { } +foo(3, 4);` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/refactorConvertToNamedParameters_restParamInference.ts b/tests/cases/fourslash/refactorConvertToNamedParameters_restParamInference.ts new file mode 100644 index 0000000000000..36ed5084fe202 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToNamedParameters_restParamInference.ts @@ -0,0 +1,15 @@ +/// + +////function log(/*a*/a: number, b: number, ...args/*b*/) { } +////let l = log(-1, -2, 3, 4, 5); +////let k = log(1, 2); + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Convert to named parameters", + actionName: "Convert to named parameters", + actionDescription: "Convert to named parameters", + newContent: `function log({ a, b, args = [] }: { a: number; b: number; args?: any[]; }) { } +let l = log({ a: -1, b: -2, args: [3, 4, 5] }); +let k = log({ a: 1, b: 2 });` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/refactorConvertToNamedParameters_staticMethod.ts b/tests/cases/fourslash/refactorConvertToNamedParameters_staticMethod.ts new file mode 100644 index 0000000000000..c84c1aa20d4c1 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToNamedParameters_staticMethod.ts @@ -0,0 +1,21 @@ +/// + +////class Foo { +//// static /*a*/bar/*b*/(t: string, s: string): string { +//// return s + t; +//// } +////} +////Foo.bar("a", "b"); + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Convert to named parameters", + actionName: "Convert to named parameters", + actionDescription: "Convert to named parameters", + newContent: `class Foo { + static bar({ t, s }: { t: string; s: string; }): string { + return s + t; + } +} +Foo.bar({ t: "a", s: "b" });` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/refactorConvertToNamedParameters_superCall.ts b/tests/cases/fourslash/refactorConvertToNamedParameters_superCall.ts new file mode 100644 index 0000000000000..c5d15903ef55b --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToNamedParameters_superCall.ts @@ -0,0 +1,25 @@ +/// + +////class A { +//// constructor(/*a*/a: string, b: string/*b*/) { } +////} +////class B extends A { +//// constructor(a: string, b: string, c: string) { +//// super(a, b); +//// } +////} + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Convert to named parameters", + actionName: "Convert to named parameters", + actionDescription: "Convert to named parameters", + newContent: `class A { + constructor({ a, b }: { a: string; b: string; }) { } +} +class B extends A { + constructor(a: string, b: string, c: string) { + super({ a: a, b: b }); + } +}` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/refactorConvertToNamedParameters_thisParam.ts b/tests/cases/fourslash/refactorConvertToNamedParameters_thisParam.ts new file mode 100644 index 0000000000000..e4a96bc7bf133 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToNamedParameters_thisParam.ts @@ -0,0 +1,17 @@ +/// + +////function foo(this: void, /*a*/t: string, s: string/*b*/) { +//// return s; +////} +////foo("a", "b"); + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Convert to named parameters", + actionName: "Convert to named parameters", + actionDescription: "Convert to named parameters", + newContent: `function foo(this: void, { t, s }: { t: string; s: string; }) { + return s; +} +foo({ t: "a", s: "b" });` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/refactorConvertToNamedParameters_typedRestParam.ts b/tests/cases/fourslash/refactorConvertToNamedParameters_typedRestParam.ts new file mode 100644 index 0000000000000..34e0331f466d4 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToNamedParameters_typedRestParam.ts @@ -0,0 +1,15 @@ +/// + +////function /*a*/buildName/*b*/(firstName: string, middleName?: string, ...restOfName: string[]) { } +////let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie"); +////let myName = buildName("Joseph"); + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Convert to named parameters", + actionName: "Convert to named parameters", + actionDescription: "Convert to named parameters", + newContent: `function buildName({ firstName, middleName, restOfName = [] }: { firstName: string; middleName?: string; restOfName?: string[]; }) { } +let employeeName = buildName({ firstName: "Joseph", middleName: "Samuel", restOfName: ["Lucas", "MacKinzie"] }); +let myName = buildName({ firstName: "Joseph" });` +}); \ No newline at end of file diff --git a/tests/cases/fourslash/refactorConvertToNamedParameters_varArrowFunction.ts b/tests/cases/fourslash/refactorConvertToNamedParameters_varArrowFunction.ts new file mode 100644 index 0000000000000..ec25b50b067b8 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToNamedParameters_varArrowFunction.ts @@ -0,0 +1,7 @@ +/// + +////var foo = /*a*/(a: number, b: number)/*b*/ => {}; +////foo(1, 2); + +goTo.select("a", "b"); +verify.not.refactorAvailable("Convert to named parameters");