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 resolveModuleNames, 23 resolveTypeReferenceDirectives 24} from './ets_checker' 25 26const arkTSDir: string = 'ArkTS'; 27const arkTSLinterOutputFileName: string = 'ArkTSLinter_output.json'; 28const spaceNumBeforeJsonLine = 2; 29 30interface OutputInfo { 31 categoryInfo: string | undefined; 32 fileName: string | undefined; 33 line: number | undefined; 34 character: number | undefined; 35 messageText: string | ts.DiagnosticMessageChain; 36} 37 38export enum ArkTSLinterMode { 39 NOT_USE = 0, 40 COMPATIBLE_MODE = 1, 41 STANDARD_MODE = 2 42} 43 44export type ProcessDiagnosticsFunc = (diagnostics: ts.Diagnostic) => void; 45 46export function doArkTSLinter(program: ts.Program, arkTSMode: ArkTSLinterMode, printDiagnostic: ProcessDiagnosticsFunc, 47 shouldWriteFile: boolean = true): ts.Diagnostic[] { 48 if (arkTSMode === ArkTSLinterMode.NOT_USE) { 49 return []; 50 } 51 52 const compilerHost: ts.CompilerHost = ts.createCompilerHost(program.getCompilerOptions()); 53 compilerHost.resolveModuleNames = resolveModuleNames; 54 compilerHost.getCurrentDirectory = () => process.cwd(); 55 compilerHost.getDefaultLibFileName = options => ts.getDefaultLibFilePath(options); 56 compilerHost.resolveTypeReferenceDirectives = resolveTypeReferenceDirectives; 57 58 let diagnostics: ts.Diagnostic[] = ts.runArkTSLinter(program, compilerHost); 59 60 removeOutputFile(); 61 if (diagnostics.length === 0) { 62 return []; 63 } 64 65 if (arkTSMode === ArkTSLinterMode.COMPATIBLE_MODE) { 66 processArkTSLinterReportAsWarning(diagnostics, printDiagnostic, shouldWriteFile); 67 } else { 68 processArkTSLinterReportAsError(diagnostics, printDiagnostic); 69 } 70 71 return diagnostics; 72} 73 74function processArkTSLinterReportAsError(diagnostics: ts.Diagnostic[], printDiagnostic: ProcessDiagnosticsFunc): void { 75 diagnostics.forEach((diagnostic: ts.Diagnostic) => { 76 printDiagnostic(diagnostic); 77 }); 78 printArkTSLinterFAQ(diagnostics, printDiagnostic); 79} 80 81function processArkTSLinterReportAsWarning(diagnostics: ts.Diagnostic[], printDiagnostic: ProcessDiagnosticsFunc, 82 shouldWriteFile: boolean): void { 83 const filePath = shouldWriteFile ? writeOutputFile(diagnostics) : undefined; 84 if (filePath === undefined) { 85 diagnostics.forEach((diagnostic: ts.Diagnostic) => { 86 const originalCategory = diagnostic.category; 87 diagnostic.category = ts.DiagnosticCategory.Warning; 88 printDiagnostic(diagnostic); 89 diagnostic.category = originalCategory; 90 }); 91 printArkTSLinterFAQ(diagnostics, printDiagnostic); 92 return; 93 } 94 const logMessage = `Has ${diagnostics.length} ArkTS Linter Error. You can get the output in ${filePath}`; 95 const arkTSDiagnostic: ts.Diagnostic = { 96 file: undefined, 97 start: undefined, 98 length: undefined, 99 messageText: logMessage, 100 category: ts.DiagnosticCategory.Warning, 101 code: -1, 102 reportsUnnecessary: undefined, 103 reportsDeprecated: undefined 104 }; 105 printDiagnostic(arkTSDiagnostic); 106 107 printArkTSLinterFAQ(diagnostics, printDiagnostic); 108} 109 110function writeOutputFile(diagnostics: ts.Diagnostic[]): string | undefined { 111 let filePath: string = toUnixPath(projectConfig.cachePath); 112 if (!fs.existsSync(filePath)) { 113 return undefined; 114 } 115 filePath = toUnixPath(path.join(filePath, arkTSDir)); 116 if (!fs.existsSync(filePath)) { 117 fs.mkdirSync(filePath); 118 } 119 filePath = toUnixPath((path.join(filePath, arkTSLinterOutputFileName))); 120 const outputInfo: OutputInfo[] = []; 121 diagnostics.forEach((diagnostic: ts.Diagnostic) => { 122 const { line, character }: ts.LineAndCharacter = 123 diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!); 124 outputInfo.push({ 125 categoryInfo: diagnostic.category === ts.DiagnosticCategory.Error ? 'Error' : 'Warning', 126 fileName: diagnostic.file?.fileName, 127 line: line + 1, 128 character: character + 1, 129 messageText: diagnostic.messageText 130 }); 131 }); 132 let output: string | undefined = filePath; 133 try { 134 fs.writeFileSync(filePath, JSON.stringify(outputInfo, undefined, spaceNumBeforeJsonLine)); 135 } catch { 136 output = undefined; 137 } 138 return output; 139} 140 141function removeOutputFile(): void { 142 let filePath: string = toUnixPath(projectConfig.cachePath); 143 if (!fs.existsSync(filePath)) { 144 return; 145 } 146 filePath = toUnixPath(path.join(filePath, arkTSDir)); 147 if (!fs.existsSync(filePath)) { 148 return; 149 } 150 filePath = toUnixPath((path.join(filePath, arkTSLinterOutputFileName))); 151 if (fs.existsSync(filePath)) { 152 fs.rmSync(filePath); 153 } 154} 155 156function printArkTSLinterFAQ(diagnostics: ts.Diagnostic[], printDiagnostic: ProcessDiagnosticsFunc): void { 157 if (diagnostics === undefined || diagnostics.length === undefined || diagnostics.length <= 0) { 158 return; 159 } 160 161 const logMessageFAQ = 'For details about ArkTS syntax errors, see FAQs'; 162 const arkTSFAQDiagnostic: ts.Diagnostic = { 163 file: undefined, 164 start: undefined, 165 length: undefined, 166 messageText: logMessageFAQ, 167 category: ts.DiagnosticCategory.Warning, 168 code: -1, 169 reportsUnnecessary: undefined, 170 reportsDeprecated: undefined 171 }; 172 printDiagnostic(arkTSFAQDiagnostic); 173} 174