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} 79 80function processArkTSLinterReportAsWarning(diagnostics: ts.Diagnostic[], printDiagnostic: ProcessDiagnosticsFunc, 81 shouldWriteFile: boolean): void { 82 const filePath = shouldWriteFile ? writeOutputFile(diagnostics) : undefined; 83 if (filePath === undefined) { 84 diagnostics.forEach((diagnostic: ts.Diagnostic) => { 85 const originalCategory = diagnostic.category; 86 diagnostic.category = ts.DiagnosticCategory.Warning; 87 printDiagnostic(diagnostic); 88 diagnostic.category = originalCategory; 89 }); 90 return; 91 } 92 const logMessage = `Has ${diagnostics.length} ArkTS Linter Error. You can get the output in ${filePath}`; 93 const arkTSDiagnostic: ts.Diagnostic = { 94 file: undefined, 95 start: undefined, 96 length: undefined, 97 messageText: logMessage, 98 category: ts.DiagnosticCategory.Warning, 99 code: -1, 100 reportsUnnecessary: undefined, 101 reportsDeprecated: undefined 102 }; 103 printDiagnostic(arkTSDiagnostic); 104} 105 106function writeOutputFile(diagnostics: ts.Diagnostic[]): string | undefined { 107 let filePath: string = toUnixPath(projectConfig.cachePath); 108 if (!fs.existsSync(filePath)) { 109 return undefined; 110 } 111 filePath = toUnixPath(path.join(filePath, arkTSDir)); 112 if (!fs.existsSync(filePath)) { 113 fs.mkdirSync(filePath); 114 } 115 filePath = toUnixPath((path.join(filePath, arkTSLinterOutputFileName))); 116 const outputInfo: OutputInfo[] = []; 117 diagnostics.forEach((diagnostic: ts.Diagnostic) => { 118 const { line, character }: ts.LineAndCharacter = 119 diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!); 120 outputInfo.push({ 121 categoryInfo: diagnostic.category === ts.DiagnosticCategory.Error ? 'Error' : 'Warning', 122 fileName: diagnostic.file?.fileName, 123 line: line + 1, 124 character: character + 1, 125 messageText: diagnostic.messageText 126 }); 127 }); 128 let output: string | undefined = filePath; 129 try { 130 fs.writeFileSync(filePath, JSON.stringify(outputInfo, undefined, spaceNumBeforeJsonLine)); 131 } catch { 132 output = undefined; 133 } 134 return output; 135} 136 137function removeOutputFile(): void { 138 let filePath: string = toUnixPath(projectConfig.cachePath); 139 if (!fs.existsSync(filePath)) { 140 return; 141 } 142 filePath = toUnixPath(path.join(filePath, arkTSDir)); 143 if (!fs.existsSync(filePath)) { 144 return; 145 } 146 filePath = toUnixPath((path.join(filePath, arkTSLinterOutputFileName))); 147 if (fs.existsSync(filePath)) { 148 fs.rmSync(filePath); 149 } 150} 151