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 fs from 'fs'; 17import path from 'path'; 18import * as ts from 'typescript'; 19import { projectConfig } from '../main'; 20import { toUnixPath } from './utils'; 21import { 22 ERROR_DESCRIPTION, 23 LINTER_SUBSYSTEM_CODE, 24 ERROR_TYPE_CODE, 25 EXTENSION_CODE, 26 HvigorErrorInfo, 27} from './hvigor_error_code/hvigor_error_info'; 28 29const arkTSDir: string = 'ArkTS'; 30const arkTSLinterOutputFileName: string = 'ArkTSLinter_output.json'; 31const spaceNumBeforeJsonLine = 2; 32const complementSize: number = 3; 33const complementCode: string = '0'; 34 35interface OutputInfo { 36 categoryInfo: string | undefined; 37 fileName: string | undefined; 38 line: number | undefined; 39 character: number | undefined; 40 messageText: string | ts.DiagnosticMessageChain; 41} 42 43export enum ArkTSLinterMode { 44 NOT_USE = 0, 45 COMPATIBLE_MODE = 1, 46 STANDARD_MODE = 2 47} 48 49export enum ArkTSVersion { 50 ArkTS_1_0, 51 ArkTS_1_1, 52} 53 54export type ProcessDiagnosticsFunc = (diagnostics: ts.Diagnostic, errorCodeLogger?: Object | undefined) => void; 55 56function getArkTSVersionString(arkTSVersion: ArkTSVersion): string { 57 return arkTSVersion === ArkTSVersion.ArkTS_1_0 ? 'ArkTS_1_0' : 'ArkTS_1_1'; 58} 59 60export function doArkTSLinter(arkTSVersion: ArkTSVersion, arkTSMode: ArkTSLinterMode, 61 builderProgram: ts.BuilderProgram, printDiagnostic: ProcessDiagnosticsFunc, shouldWriteFile: boolean = true, 62 buildInfoWriteFile?: ts.WriteFileCallback, errorCodeLogger?: Object | undefined): ts.Diagnostic[] { 63 if (arkTSMode === ArkTSLinterMode.NOT_USE) { 64 return []; 65 } 66 67 let diagnostics: ts.Diagnostic[] = []; 68 const isOldVersion = arkTSVersion === ArkTSVersion.ArkTS_1_0; 69 if (isOldVersion) { 70 diagnostics = ts.ArkTSLinter_1_0.runArkTSLinter(builderProgram, /*srcFile*/ undefined, buildInfoWriteFile, 71 getArkTSVersionString(arkTSVersion)); 72 } else { 73 diagnostics = ts.ArkTSLinter_1_1.runArkTSLinter(builderProgram, /*srcFile*/ undefined, buildInfoWriteFile, 74 getArkTSVersionString(arkTSVersion)); 75 } 76 77 removeOutputFile(); 78 if (diagnostics.length === 0) { 79 return []; 80 } 81 82 if (arkTSMode === ArkTSLinterMode.COMPATIBLE_MODE) { 83 processArkTSLinterReportAsWarning(diagnostics, printDiagnostic, shouldWriteFile); 84 } else { 85 processArkTSLinterReportAsError(diagnostics, printDiagnostic, isOldVersion ? undefined : errorCodeLogger); 86 } 87 88 return diagnostics; 89} 90 91function processArkTSLinterReportAsError(diagnostics: ts.Diagnostic[], printDiagnostic: ProcessDiagnosticsFunc, errorCodeLogger?: Object | undefined): void { 92 diagnostics.forEach((diagnostic: ts.Diagnostic) => { 93 printDiagnostic(diagnostic, errorCodeLogger); 94 }); 95 printArkTSLinterFAQ(diagnostics, printDiagnostic); 96} 97 98function processArkTSLinterReportAsWarning(diagnostics: ts.Diagnostic[], printDiagnostic: ProcessDiagnosticsFunc, 99 shouldWriteFile: boolean): void { 100 const filePath = shouldWriteFile ? writeOutputFile(diagnostics) : undefined; 101 if (filePath === undefined) { 102 diagnostics.forEach((diagnostic: ts.Diagnostic) => { 103 const originalCategory = diagnostic.category; 104 diagnostic.category = ts.DiagnosticCategory.Warning; 105 printDiagnostic(diagnostic); 106 diagnostic.category = originalCategory; 107 }); 108 printArkTSLinterFAQ(diagnostics, printDiagnostic); 109 return; 110 } 111 const logMessage = `Has ${diagnostics.length} ArkTS Linter Error. You can get the output in ${filePath}`; 112 const arkTSDiagnostic: ts.Diagnostic = { 113 file: undefined, 114 start: undefined, 115 length: undefined, 116 messageText: logMessage, 117 category: ts.DiagnosticCategory.Warning, 118 code: -1, 119 reportsUnnecessary: undefined, 120 reportsDeprecated: undefined 121 }; 122 printDiagnostic(arkTSDiagnostic); 123 124 printArkTSLinterFAQ(diagnostics, printDiagnostic); 125} 126 127function writeOutputFile(diagnostics: ts.Diagnostic[]): string | undefined { 128 let filePath: string = toUnixPath(projectConfig.cachePath); 129 if (!fs.existsSync(filePath)) { 130 return undefined; 131 } 132 filePath = toUnixPath(path.join(filePath, arkTSDir)); 133 if (!fs.existsSync(filePath)) { 134 fs.mkdirSync(filePath); 135 } 136 filePath = toUnixPath((path.join(filePath, arkTSLinterOutputFileName))); 137 const outputInfo: OutputInfo[] = []; 138 diagnostics.forEach((diagnostic: ts.Diagnostic) => { 139 const { line, character }: ts.LineAndCharacter = 140 diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!); 141 outputInfo.push({ 142 categoryInfo: diagnostic.category === ts.DiagnosticCategory.Error ? 'Error' : 'Warning', 143 fileName: diagnostic.file?.fileName, 144 line: line + 1, 145 character: character + 1, 146 messageText: diagnostic.messageText 147 }); 148 }); 149 let output: string | undefined = filePath; 150 try { 151 fs.writeFileSync(filePath, JSON.stringify(outputInfo, undefined, spaceNumBeforeJsonLine)); 152 } catch { 153 output = undefined; 154 } 155 return output; 156} 157 158function removeOutputFile(): void { 159 let filePath: string = toUnixPath(projectConfig.cachePath); 160 if (!fs.existsSync(filePath)) { 161 return; 162 } 163 filePath = toUnixPath(path.join(filePath, arkTSDir)); 164 if (!fs.existsSync(filePath)) { 165 return; 166 } 167 filePath = toUnixPath((path.join(filePath, arkTSLinterOutputFileName))); 168 if (fs.existsSync(filePath)) { 169 fs.rmSync(filePath); 170 } 171} 172 173function printArkTSLinterFAQ(diagnostics: ts.Diagnostic[], printDiagnostic: ProcessDiagnosticsFunc): void { 174 if (diagnostics === undefined || diagnostics.length === undefined || diagnostics.length <= 0) { 175 return; 176 } 177 178 const logMessageFAQ = 'For details about ArkTS syntax errors, see FAQs'; 179 const arkTSFAQDiagnostic: ts.Diagnostic = { 180 file: undefined, 181 start: undefined, 182 length: undefined, 183 messageText: logMessageFAQ, 184 category: ts.DiagnosticCategory.Warning, 185 code: -1, 186 reportsUnnecessary: undefined, 187 reportsDeprecated: undefined 188 }; 189 printDiagnostic(arkTSFAQDiagnostic); 190} 191 192export function transfromErrorCode(code: number, positionMessage: string, message: string): HvigorErrorInfo { 193 return new HvigorErrorInfo() 194 .setCode(formatNumber(code)) 195 .setDescription(ERROR_DESCRIPTION) 196 .setCause(message) 197 .setPosition(positionMessage) 198 .setSolutions([]); 199} 200 201function formatNumber(num: number): string { 202 // The minimum code in strict mode starts from 1000 203 const extendedCode = num > 1000 ? EXTENSION_CODE : num.toString().padStart(complementSize, complementCode); 204 return LINTER_SUBSYSTEM_CODE + ERROR_TYPE_CODE + extendedCode; 205} 206