• 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 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}