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 { 18 19import DiagnosticChecker = DiagnosticCheckerNamespace.DiagnosticChecker 20// Current approach relates on error code and error message matching and it is quite fragile, 21// so this place should be checked thoroughly in the case of typescript upgrade 22export namespace LibraryTypeCallDiagnosticCheckerNamespace { 23export const TYPE_0_IS_NOT_ASSIGNABLE_TO_TYPE_1_ERROR_CODE = 2322; 24export const TYPE_UNKNOWN_IS_NOT_ASSIGNABLE_TO_TYPE_1_RE = /^Type '(.*)\bunknown\b(.*)' is not assignable to type '.*'\.$/; 25export const TYPE_NULL_IS_NOT_ASSIGNABLE_TO_TYPE_1_RE = /^Type '(.*)\bnull\b(.*)' is not assignable to type '.*'\.$/; 26export const TYPE_UNDEFINED_IS_NOT_ASSIGNABLE_TO_TYPE_1_RE = /^Type '(.*)\bundefined\b(.*)' is not assignable to type '.*'\.$/; 27 28export const ARGUMENT_OF_TYPE_0_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_ERROR_CODE = 2345; 29export const ARGUMENT_OF_TYPE_NULL_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_RE = /^Argument of type '(.*)\bnull\b(.*)' is not assignable to parameter of type '.*'\.$/; 30export const ARGUMENT_OF_TYPE_UNDEFINED_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_RE = /^Argument of type '(.*)\bundefined\b(.*)' is not assignable to parameter of type '.*'\.$/; 31 32export const NO_OVERLOAD_MATCHES_THIS_CALL_ERROR_CODE = 2769; 33export const TYPE = 'Type'; 34export const IS_NOT_ASSIGNABLE_TO_TYPE = 'is not assignable to type'; 35export const ARGUMENT_OF_TYPE = 'Argument of type'; 36export const IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE = 'is not assignable to parameter of type'; 37 38export enum ErrorType { 39 NO_ERROR, 40 UNKNOW, 41 NULL, 42 UNDEFINED 43} 44 45export class LibraryTypeCallDiagnosticChecker implements DiagnosticChecker { 46 inLibCall: boolean = false; 47 diagnosticMessages: Array<ts.DiagnosticMessageChain> | undefined; 48 filteredDiagnosticMessages: DiagnosticMessageChain[] = []; 49 50 constructor(filteredDiagnosticMessages: DiagnosticMessageChain[]) { 51 this.filteredDiagnosticMessages = filteredDiagnosticMessages; 52 } 53 54 configure(inLibCall: boolean, diagnosticMessages: Array<ts.DiagnosticMessageChain>) { 55 this.inLibCall = inLibCall; 56 this.diagnosticMessages = diagnosticMessages; 57 } 58 59 checkMessageText(msg: string): boolean { 60 if (this.inLibCall) { 61 const match = msg.match(ARGUMENT_OF_TYPE_NULL_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_RE) || 62 msg.match(ARGUMENT_OF_TYPE_UNDEFINED_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_RE) || 63 msg.match(TYPE_UNDEFINED_IS_NOT_ASSIGNABLE_TO_TYPE_1_RE) || 64 msg.match(TYPE_NULL_IS_NOT_ASSIGNABLE_TO_TYPE_1_RE); 65 return !match; 66 } 67 return true; 68 } 69 70 static checkMessageChain(chain: ts.DiagnosticMessageChain, inLibCall: boolean): ErrorType { 71 if (chain.code === TYPE_0_IS_NOT_ASSIGNABLE_TO_TYPE_1_ERROR_CODE) { 72 if (chain.messageText.match(TYPE_UNKNOWN_IS_NOT_ASSIGNABLE_TO_TYPE_1_RE)) { 73 return ErrorType.UNKNOW; 74 } 75 if (inLibCall && chain.messageText.match(TYPE_UNDEFINED_IS_NOT_ASSIGNABLE_TO_TYPE_1_RE)) { 76 return ErrorType.UNDEFINED; 77 } 78 if (inLibCall && chain.messageText.match(TYPE_NULL_IS_NOT_ASSIGNABLE_TO_TYPE_1_RE)) { 79 return ErrorType.NULL; 80 } 81 } 82 if (chain.code === ARGUMENT_OF_TYPE_0_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_ERROR_CODE) { 83 if (inLibCall && chain.messageText.match(ARGUMENT_OF_TYPE_UNDEFINED_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_RE)) { 84 return ErrorType.UNDEFINED; 85 } 86 if (inLibCall && chain.messageText.match(ARGUMENT_OF_TYPE_NULL_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_RE)) { 87 return ErrorType.NULL; 88 } 89 } 90 91 if (!chain.next?.length) { 92 return ErrorType.NO_ERROR; 93 } 94 // 'No_overrload...' error need to check each sub-error message, others only check the first one 95 if (chain.code !== NO_OVERLOAD_MATCHES_THIS_CALL_ERROR_CODE) { 96 return LibraryTypeCallDiagnosticChecker.checkMessageChain(chain.next[0], inLibCall); 97 } 98 let result = ErrorType.NO_ERROR; 99 for (const child of chain.next) { 100 result = LibraryTypeCallDiagnosticChecker.checkMessageChain(child, inLibCall); 101 if (result !== ErrorType.NO_ERROR) { 102 break; 103 } 104 } 105 return result; 106 }; 107 108 checkFilteredDiagnosticMessages(msgText: ts.DiagnosticMessageChain | string) { 109 if (this.filteredDiagnosticMessages.length == 0) { 110 return true; 111 } 112 113 if (typeof msgText !== 'string' && this.filteredDiagnosticMessages.includes(msgText)) { 114 return false; 115 } 116 117 for (const msgChain of this.filteredDiagnosticMessages) { 118 if (typeof msgText == 'string') { 119 if (msgText == msgChain.messageText) { 120 return false; 121 } 122 continue; 123 } 124 125 let curMsg: ts.DiagnosticMessageChain | undefined = msgText; 126 let curFilteredMsg: ts.DiagnosticMessageChain | undefined = msgChain; 127 while (curMsg) { 128 if (!curFilteredMsg) { 129 return true; 130 } 131 132 if (curMsg.code != curFilteredMsg.code) { 133 return true; 134 } 135 136 if (curMsg.messageText != curFilteredMsg.messageText) { 137 return true; 138 } 139 140 curMsg = curMsg.next ? curMsg.next[0]: undefined; 141 curFilteredMsg = curFilteredMsg.next ? curFilteredMsg.next[0]: undefined; 142 } 143 144 return false; 145 } 146 return true; 147 } 148 149 checkDiagnosticMessage(msgText: string | ts.DiagnosticMessageChain): boolean { 150 if (!this.diagnosticMessages) { 151 return false; 152 } 153 154 if (this.inLibCall && !this.checkFilteredDiagnosticMessages(msgText)) { 155 return false; 156 } 157 158 if (typeof msgText == 'string') { 159 return this.checkMessageText(msgText); 160 } 161 162 if (LibraryTypeCallDiagnosticChecker.checkMessageChain(msgText, this.inLibCall) !== ErrorType.NO_ERROR) { 163 this.diagnosticMessages.push(msgText); 164 return false; 165 } 166 return true; 167 } 168 169 static rebuildTscDiagnostics(tscStrictDiagnostics: Map<Diagnostic[]>): void { 170 if (tscStrictDiagnostics.size === 0) { 171 return; 172 } 173 174 let diagnosticMessageChainArr: Diagnostic[] = []; 175 let strictArr: Diagnostic[] = []; 176 tscStrictDiagnostics.forEach((strict) => { 177 if (strict.length === 0) { 178 return; 179 } 180 181 for (let i = 0; i < strict.length; i++) { 182 if (typeof strict[i].messageText === 'string') { 183 strictArr.push(strict[i]); 184 } else { 185 diagnosticMessageChainArr.push(strict[i]); 186 } 187 } 188 }); 189 190 if (diagnosticMessageChainArr.length === 0 || strictArr.length === 0) { 191 return; 192 } 193 194 /** 195 * DiagnosticMessageChain in strict mode errors are reported across files and need to be traversed for DiagnosticMessageChain errors on all files 196 * DiagnosticMessageChain type errors require that both type and argument error text be constructed to match those with string errors 197 * TypeScriptLinter. StrictDiagnosticCache can only keep by white list DiagnosticMessageChain type error and its matching success string type errors 198 */ 199 const textSet: Set<string> = new Set<string>(); 200 const unknownSet: Set<string> = new Set<string>(); 201 diagnosticMessageChainArr.forEach((item) => { 202 const diagnosticMessageChain = item.messageText as DiagnosticMessageChain; 203 const isAllowFilter: ErrorType = LibraryTypeCallDiagnosticChecker.checkMessageChain(diagnosticMessageChain, true); 204 if (isAllowFilter === ErrorType.UNKNOW) { 205 LibraryTypeCallDiagnosticChecker.collectDiagnosticMessage(diagnosticMessageChain, unknownSet); 206 TypeScriptLinter.unknowDiagnosticCache.add(item); 207 return; 208 } 209 if (isAllowFilter !== ErrorType.NO_ERROR) { 210 LibraryTypeCallDiagnosticChecker.collectDiagnosticMessage(diagnosticMessageChain, textSet); 211 TypeScriptLinter.strictDiagnosticCache.add(item); 212 } 213 }); 214 strictArr.forEach((item) => { 215 const messageText = item.messageText as string; 216 if (unknownSet.has(messageText)) { 217 TypeScriptLinter.unknowDiagnosticCache.add(item); 218 } else if (textSet.has(messageText)) { 219 TypeScriptLinter.strictDiagnosticCache.add(item); 220 } 221 }); 222 } 223 224 static collectDiagnosticMessage(diagnosticMessageChain: DiagnosticMessageChain, textSet: Set<string>): void { 225 const isTypeError = diagnosticMessageChain.code === TYPE_0_IS_NOT_ASSIGNABLE_TO_TYPE_1_ERROR_CODE; 226 const typeText = isTypeError ? 227 diagnosticMessageChain.messageText : 228 diagnosticMessageChain.messageText.replace(ARGUMENT_OF_TYPE, TYPE) 229 .replace(IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE, IS_NOT_ASSIGNABLE_TO_TYPE); 230 const argumentText = isTypeError ? 231 diagnosticMessageChain.messageText.replace(TYPE, ARGUMENT_OF_TYPE) 232 .replace(IS_NOT_ASSIGNABLE_TO_TYPE, IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE) : 233 diagnosticMessageChain.messageText; 234 textSet.add(typeText); 235 textSet.add(argumentText); 236 } 237} 238} 239} 240}