• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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}