1/* 2 * Copyright (c) 2023-2023 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16namespace ts { 17export namespace Autofixer { 18 19import AutofixInfo = Common.AutofixInfo; 20import FaultID = Problems.FaultID; 21 22//import Utils = Utils; 23 24export const AUTOFIX_ALL: AutofixInfo = { problemID: "", start: -1, end: -1 }; 25 26// Some fixes are potentially risky and may break source code if fixes 27// are applied separately. 28// Temporary solution is to disable all risky autofixes, until the 29// algorithm is improved to guarantee that fixes can be applied 30// safely and won't break program code. 31const UNSAFE_FIXES: FaultID[] = [ FaultID.LiteralAsPropertyName, FaultID.PropertyAccessByIndex ]; 32 33export const autofixInfo: AutofixInfo[] = []; 34 35export function shouldAutofix(node: Node, faultID: FaultID): boolean { 36 if (UNSAFE_FIXES.includes(faultID)) return false; 37 if (autofixInfo.length === 0) return false; 38 if (autofixInfo.length === 1 && autofixInfo[0] === AUTOFIX_ALL) return true; 39 return autofixInfo.findIndex( 40 value => value.start === node.getStart() && value.end === node.getEnd() && value.problemID === FaultID[faultID] 41 ) !== -1; 42} 43 44export interface Autofix { 45 replacementText: string; 46 start: number; 47 end: number; 48} 49 50const printer: Printer = createPrinter({ omitTrailingSemicolon: false, removeComments: false }); 51 52function numericLiteral2IdentifierName(numeric: NumericLiteral): string { 53 return "__" + numeric.getText(); 54} 55 56function stringLiteral2IdentifierName(str: StringLiteral): string { 57 const text = str.getText(); 58 return text.substring(1, text.length-1); // cut out starting and ending quoters. 59} 60 61function propertyName2IdentifierName(name: PropertyName): string { 62 if (name.kind === SyntaxKind.NumericLiteral) { 63 return numericLiteral2IdentifierName(name); 64 } 65 66 if (name.kind === SyntaxKind.StringLiteral) { 67 return stringLiteral2IdentifierName(name); 68 } 69 70 return ""; 71} 72 73function indexExpr2IdentifierName(index: Expression): string { 74 if (index.kind === SyntaxKind.NumericLiteral) { 75 return numericLiteral2IdentifierName(index as NumericLiteral); 76 } 77 78 if (index.kind === SyntaxKind.StringLiteral) { 79 return stringLiteral2IdentifierName(index as StringLiteral); 80 } 81 82 return ""; 83} 84 85export function fixLiteralAsPropertyName(node: Node): Autofix[] | undefined { 86 if (isPropertyDeclaration(node) || isPropertyAssignment(node)) { 87 const propName = node.name; 88 const identName = propertyName2IdentifierName(propName); 89 if (identName) { 90 return [{ replacementText: identName, start: propName.getStart(), end: propName.getEnd() }]; 91 } 92 } 93 94 return undefined; 95} 96 97export function fixPropertyAccessByIndex(node: Node): Autofix[] | undefined { 98 if (isElementAccessExpression(node)) { 99 const elemAccess = node; 100 const identifierName = indexExpr2IdentifierName(elemAccess.argumentExpression); 101 if (identifierName) { 102 return [{ 103 replacementText: elemAccess.expression.getText() + "." + identifierName, 104 start: elemAccess.getStart(), end: elemAccess.getEnd() 105 }]; 106 } 107 } 108 109 return undefined; 110} 111 112export function fixFunctionExpression(funcExpr: FunctionExpression, 113 params: NodeArray<ParameterDeclaration> = funcExpr.parameters, 114 retType: TypeNode | undefined = funcExpr.type): Autofix { 115 const arrowFunc = factory.createArrowFunction( 116 undefined, undefined, params, retType, factory.createToken(SyntaxKind.EqualsGreaterThanToken), 117 funcExpr.body 118 ); 119 const text = printer.printNode(EmitHint.Unspecified, arrowFunc, funcExpr.getSourceFile()); 120 return { start: funcExpr.getStart(), end: funcExpr.getEnd(), replacementText: text }; 121} 122 123export function fixReturnType(funcLikeDecl: FunctionLikeDeclaration, typeNode: TypeNode): Autofix { 124 const text = ": " + printer.printNode(EmitHint.Unspecified, typeNode, funcLikeDecl.getSourceFile()); 125 const pos = getReturnTypePosition(funcLikeDecl); 126 return { start: pos, end: pos, replacementText: text }; 127} 128 129function getReturnTypePosition(funcLikeDecl: FunctionLikeDeclaration): number { 130 if (funcLikeDecl.body) { 131 // Find position of the first node or token that follows parameters. 132 // After that, iterate over child nodes in reverse order, until found 133 // first closing parenthesis. 134 const postParametersPosition = isArrowFunction(funcLikeDecl) 135 ? funcLikeDecl.equalsGreaterThanToken.getStart() 136 : funcLikeDecl.body.getStart(); 137 138 const children = funcLikeDecl.getChildren(); 139 for (let i = children.length - 1; i >= 0; i--) { 140 const child = children[i]; 141 if (child.kind === SyntaxKind.CloseParenToken && child.getEnd() < postParametersPosition) { 142 return child.getEnd(); 143 } 144 } 145 } 146 147 // Shouldn't get here. 148 return -1; 149} 150 151export function fixCtorParameterProperties(ctorDecl: ConstructorDeclaration, paramTypes: TypeNode[]): Autofix[] | undefined { 152 const fieldInitStmts: Statement[] = []; 153 const newFieldPos = ctorDecl.getStart(); 154 const autofixes: Autofix[] = [{ start: newFieldPos, end: newFieldPos, replacementText: "" }]; 155 156 for (let i = 0; i < ctorDecl.parameters.length; i++) { 157 const param = ctorDecl.parameters[i]; 158 159 // Parameter property can not be a destructuring parameter. 160 if (!isIdentifier(param.name)) { 161 continue; 162 } 163 164 if (Utils.hasAccessModifier(param)) { 165 const propIdent = factory.createIdentifier(param.name.text); 166 167 const newFieldNode = factory.createPropertyDeclaration( 168 undefined, param.modifiers, propIdent, undefined, paramTypes[i], undefined 169 ); 170 const newFieldText = printer.printNode(EmitHint.Unspecified, newFieldNode, ctorDecl.getSourceFile()) + "\n"; 171 autofixes[0].replacementText += newFieldText; 172 173 const newParamDecl = factory.createParameterDeclaration( 174 undefined, undefined, undefined, param.name, param.questionToken, param.type, param.initializer 175 ); 176 const newParamText = printer.printNode(EmitHint.Unspecified, newParamDecl, ctorDecl.getSourceFile()); 177 autofixes.push({ start: param.getStart(), end: param.getEnd(), replacementText: newParamText }); 178 179 fieldInitStmts.push(factory.createExpressionStatement(factory.createAssignment( 180 factory.createPropertyAccessExpression( 181 factory.createThis(), 182 propIdent, 183 ), 184 propIdent 185 ))); 186 } 187 } 188 189 // Note: Bodyless ctors can't have parameter properties. 190 if (ctorDecl.body) { 191 const newBody = factory.createBlock(fieldInitStmts.concat(ctorDecl.body.statements), true); 192 const newBodyText = printer.printNode(EmitHint.Unspecified, newBody, ctorDecl.getSourceFile()); 193 autofixes.push({ start: ctorDecl.body.getStart(), end: ctorDecl.body.getEnd(), replacementText: newBodyText }); 194 } 195 196 return autofixes; 197} 198 199} 200}