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