1/* 2 * Copyright (c) 2022-2024 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 16import * as ts from 'typescript'; 17 18import { FaultID } from '../utils/lib/FaultId'; 19import { visitVisitResult } from './utils/ASTHelpers'; 20 21export class Autofixer { 22 private readonly typeChecker: ts.TypeChecker; 23 private readonly context: ts.TransformationContext; 24 25 constructor(typeChecker: ts.TypeChecker, context: ts.TransformationContext) { 26 this.typeChecker = typeChecker; 27 this.context = context; 28 } 29 30 private readonly autofixes = new Map<ts.SyntaxKind, ts.Visitor[]>([ 31 [ts.SyntaxKind.VariableDeclarationList, [this[FaultID.VarDeclaration].bind(this)]], 32 [ts.SyntaxKind.PropertyDeclaration, [this[FaultID.PrivateIdentifier].bind(this)]], 33 [ts.SyntaxKind.SourceFile, [this[FaultID.ImportAfterStatement].bind(this)]] 34 ]); 35 36 fixNode(node: ts.Node): ts.VisitResult<ts.Node> { 37 const autofixes = this.autofixes.get(node.kind); 38 39 if (autofixes === undefined) { 40 return node; 41 } 42 43 let result: ts.VisitResult<ts.Node> = node; 44 45 for (const autofix of autofixes) { 46 result = visitVisitResult(result, autofix); 47 } 48 49 return result; 50 } 51 52 /** 53 * Rule: `arkts-no-var` 54 */ 55 private [FaultID.VarDeclaration](node: ts.Node): ts.VisitResult<ts.Node> { 56 if (ts.isVariableDeclarationList(node)) { 57 const isLetDeclaration = node.flags & ts.NodeFlags.Let; 58 const isConstDeclaration = node.flags & ts.NodeFlags.Const; 59 60 if (!isLetDeclaration && !isConstDeclaration) { 61 const newFlags = node.flags | ts.NodeFlags.Let; 62 return this.context.factory.createVariableDeclarationList(node.declarations, newFlags); 63 } 64 } 65 66 return node; 67 } 68 69 /** 70 * Rule: `arkts-no-private-identifiers` 71 */ 72 private [FaultID.PrivateIdentifier](node: ts.Node): ts.VisitResult<ts.Node> { 73 void this; 74 75 /* 76 * Since we can access only public members of the imported dynamic class, 77 * there is no need to fix private fields, we just do not emit them 78 */ 79 if (ts.isPropertyDeclaration(node)) { 80 if ( 81 ts.isPrivateIdentifier(node.name) || 82 node.modifiers?.find((v) => { 83 return v.kind === ts.SyntaxKind.PrivateKeyword; 84 }) 85 ) { 86 return undefined; 87 } 88 } 89 90 return node; 91 } 92 93 /** 94 * Rule: `arkts-no-misplaced-imports` 95 */ 96 private [FaultID.ImportAfterStatement](node: ts.Node): ts.VisitResult<ts.Node> { 97 void this; 98 99 /** 100 * This algorithm is very very bad for both memory and performance. 101 * Redo later, when implementing other autofixes 102 */ 103 104 if (ts.isSourceFile(node)) { 105 const importDeclarations: ts.Statement[] = []; 106 const statements: ts.Statement[] = []; 107 108 for (const stmt of node.statements) { 109 if (ts.isImportDeclaration(stmt)) { 110 importDeclarations.push(stmt); 111 } else { 112 statements.push(stmt); 113 } 114 } 115 116 return this.context.factory.updateSourceFile(node, [...importDeclarations, ...statements]); 117 } 118 119 return node; 120 } 121} 122