1/* 2 * Copyright (c) 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 16import { 17 forEachChild, 18 getLeadingCommentRangesOfNode, 19 isCallExpression, 20 isExpressionStatement, 21 isIdentifier, 22 isStructDeclaration, 23 SyntaxKind, 24 visitEachChild 25} from 'typescript'; 26 27import type { 28 CommentRange, 29 Identifier, 30 Node, 31 SourceFile, 32 StructDeclaration, 33 TransformationContext 34} from 'typescript'; 35import type { IOptions } from '../configs/IOptions'; 36 37export interface ReservedNameInfo { 38 universalReservedArray: RegExp[]; // items contain wildcards 39 specificReservedArray: string[]; // items do not contain wildcards 40} 41 42/** 43 * collect exist identifier names in current source file 44 * @param sourceFile 45 */ 46export function collectExistNames(sourceFile: SourceFile): Set<string> { 47 const identifiers: Set<string> = new Set<string>(); 48 49 let visit = (node: Node): void => { 50 if (isIdentifier(node)) { 51 identifiers.add(node.text); 52 } 53 54 forEachChild(node, visit); 55 }; 56 57 forEachChild(sourceFile, visit); 58 return identifiers; 59} 60 61type IdentifiersAndStructs = {shadowIdentifiers: Identifier[], shadowStructs: StructDeclaration[]}; 62 63/** 64 * collect exist identifiers in current source file 65 * @param sourceFile 66 * @param context 67 */ 68export function collectIdentifiersAndStructs(sourceFile: SourceFile, context: TransformationContext): IdentifiersAndStructs { 69 const identifiers: Identifier[] = []; 70 const structs: StructDeclaration[] = []; 71 72 let visit = (node: Node): Node => { 73 if (isStructDeclaration(node)) { 74 structs.push(node); 75 } 76 // @ts-ignore 77 if (getOriginalNode(node).virtual) { 78 return node; 79 } 80 if (!isIdentifier(node) || !node.parent) { 81 return visitEachChild(node, visit, context); 82 } 83 84 identifiers.push(node); 85 return node; 86 }; 87 88 visit(sourceFile); 89 return {shadowIdentifiers: identifiers, shadowStructs: structs}; 90} 91 92export function isCommentedNode(node: Node, sourceFile: SourceFile): boolean { 93 const ranges: CommentRange[] = getLeadingCommentRangesOfNode(node, sourceFile); 94 return ranges !== undefined; 95} 96 97export function isSuperCallStatement(node: Node): boolean { 98 return isExpressionStatement(node) && 99 isCallExpression(node.expression) && 100 node.expression.expression.kind === SyntaxKind.SuperKeyword; 101} 102 103/** 104 * separate wildcards from specific items. 105 */ 106export function separateUniversalReservedItem(originalArray: string[]): ReservedNameInfo { 107 if (!originalArray) { 108 throw new Error('Unable to handle the empty array.'); 109 } 110 const reservedInfo: ReservedNameInfo = { 111 universalReservedArray: [], 112 specificReservedArray: [] 113 }; 114 115 originalArray.forEach(reservedItem => { 116 if (containWildcards(reservedItem)) { 117 const regexPattern = wildcardTransformer(reservedItem); 118 const regexOperator = new RegExp(`^${regexPattern}$`); 119 reservedInfo.universalReservedArray.push(regexOperator); 120 } else { 121 reservedInfo.specificReservedArray.push(reservedItem); 122 } 123 }); 124 return reservedInfo; 125} 126 127/** 128 * check if the item contains '*', '?'. 129 */ 130export function containWildcards(item: string): boolean { 131 return /[\*\?]/.test(item); 132} 133 134/** 135 * Convert specific characters into regular expressions. 136 */ 137export function wildcardTransformer(wildcard: string, isPath?: boolean): string { 138 // Add an escape character in front of special characters 139 // special characters: '\', '^', '$', '.', '+', '|', '[', ']', '{', '}', '(', ')' 140 let escapedItem = wildcard.replace(/[\\+^${}()|\[\]\.]/g, '\\$&'); 141 142 // isPath: containing '**', and '*', '?' can not be matched with '/'. 143 if (isPath) { 144 // before: ../**/a/b/c*/?.ets 145 // after: ../.*/a/b/c[^/]*/[^/].ets 146 return escapedItem.replace(/\*\*/g, '.*').replace(/(?<!\.)\*/g, '[^/]*').replace(/\?/g, '[^/]'); 147 } 148 // before: *a? 149 // after: .*a. 150 return escapedItem.replace(/\*/g, '.*').replace(/\?/g, '.'); 151} 152 153/** 154 * Determine whether the original name needs to be preserved. 155 */ 156export function needToBeReserved(reservedSet: Set<string>, universalArray: RegExp[], originalName: string): boolean { 157 return reservedSet.has(originalName) || isMatchWildcard(universalArray, originalName); 158} 159 160/** 161 * Determine whether it can match the wildcard character in the array. 162 */ 163export function isMatchWildcard(wildcardArray: RegExp[], item: string): boolean { 164 for (const wildcard of wildcardArray) { 165 if (wildcard.test(item)) { 166 return true; 167 } 168 } 169 return false; 170} 171 172/** 173 * Separate parts of an array that contain wildcard characters. 174 */ 175export function handleReservedConfig(config: IOptions, optionName: string, reservedListName: string, 176 universalLisName: string, enableRemove?: string): void { 177 const reservedConfig = config?.[optionName]; 178 let needSeparate: boolean = !!(reservedConfig?.[reservedListName]); 179 if (enableRemove) { 180 needSeparate &&= reservedConfig[enableRemove]; 181 } 182 if (needSeparate) { 183 // separate items which contain wildcards from others 184 const reservedInfo: ReservedNameInfo = separateUniversalReservedItem(reservedConfig[reservedListName]); 185 reservedConfig[reservedListName] = reservedInfo.specificReservedArray; 186 reservedConfig[universalLisName] = reservedInfo.universalReservedArray; 187 } 188} 189