1/* 2 * Copyright (c) 2022-2024 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 * as path from 'node:path'; 17import type * as ts from 'typescript'; 18import type { CommandLineOptions } from './CommandLineOptions'; 19import { faultsAttrs } from './FaultAttrs'; 20import { faultDesc } from './FaultDesc'; 21import { InteropTypescriptLinter } from './InteropTypescriptLinter'; 22import type { LinterConfig } from './LinterConfig'; 23import type { LinterOptions } from './LinterOptions'; 24import type { LintRunResult } from './LintRunResult'; 25import { Logger } from './Logger'; 26import type { ProblemInfo } from './ProblemInfo'; 27import { ProblemSeverity } from './ProblemSeverity'; 28import { FaultID } from './Problems'; 29import { TypeScriptLinter } from './TypeScriptLinter'; 30import { getTscDiagnostics } from './ts-diagnostics/GetTscDiagnostics'; 31import { transformTscDiagnostics } from './ts-diagnostics/TransformTscDiagnostics'; 32import { 33 ARKTS_IGNORE_DIRS_NO_OH_MODULES, 34 ARKTS_IGNORE_DIRS_OH_MODULES, 35 ARKTS_IGNORE_FILES 36} from './utils/consts/ArktsIgnorePaths'; 37import { mergeArrayMaps } from './utils/functions/MergeArrayMaps'; 38import { clearPathHelperCache, pathContainsDirectory } from './utils/functions/PathHelper'; 39import { LibraryTypeCallDiagnosticChecker } from './utils/functions/LibraryTypeCallDiagnosticChecker'; 40 41export function consoleLog(linterOptions: LinterOptions, ...args: unknown[]): void { 42 if (linterOptions.ideMode) { 43 return; 44 } 45 let outLine = ''; 46 for (let k = 0; k < args.length; k++) { 47 outLine += `${args[k]} `; 48 } 49 Logger.info(outLine); 50} 51 52function prepareInputFilesList(cmdOptions: CommandLineOptions): string[] { 53 let inputFiles = cmdOptions.inputFiles; 54 if (!cmdOptions.parsedConfigFile) { 55 return inputFiles; 56 } 57 58 inputFiles = cmdOptions.parsedConfigFile.fileNames; 59 if (cmdOptions.inputFiles.length <= 0) { 60 return inputFiles; 61 } 62 63 /* 64 * Apply linter only to the project source files that are specified 65 * as a command-line arguments. Other source files will be discarded. 66 */ 67 const cmdInputsResolvedPaths = cmdOptions.inputFiles.map((x) => { 68 return path.resolve(x); 69 }); 70 const configInputsResolvedPaths = inputFiles.map((x) => { 71 return path.resolve(x); 72 }); 73 inputFiles = configInputsResolvedPaths.filter((x) => { 74 return cmdInputsResolvedPaths.some((y) => { 75 return x === y; 76 }); 77 }); 78 79 return inputFiles; 80} 81 82function countProblems(linter: TypeScriptLinter | InteropTypescriptLinter): [number, number] { 83 let errorNodesTotal = 0; 84 let warningNodes = 0; 85 for (let i = 0; i < FaultID.LAST_ID; i++) { 86 switch (faultsAttrs[i].severity) { 87 case ProblemSeverity.ERROR: 88 errorNodesTotal += linter.nodeCounters[i]; 89 break; 90 case ProblemSeverity.WARNING: 91 warningNodes += linter.nodeCounters[i]; 92 break; 93 default: 94 } 95 } 96 97 return [errorNodesTotal, warningNodes]; 98} 99 100export function lint(config: LinterConfig, etsLoaderPath: string | undefined): LintRunResult { 101 const { cmdOptions, tscCompiledProgram } = config; 102 const tsProgram = tscCompiledProgram.getProgram(); 103 const options = cmdOptions.linterOptions; 104 105 // Prepare list of input files for linter and retrieve AST for those files. 106 let inputFiles = prepareInputFilesList(cmdOptions); 107 inputFiles = inputFiles.filter((input) => { 108 return shouldProcessFile(options, input); 109 }); 110 const srcFiles: ts.SourceFile[] = []; 111 for (const inputFile of inputFiles) { 112 const srcFile = tsProgram.getSourceFile(inputFile); 113 if (srcFile) { 114 srcFiles.push(srcFile); 115 } 116 } 117 118 const tscStrictDiagnostics = getTscDiagnostics(tscCompiledProgram, srcFiles); 119 LibraryTypeCallDiagnosticChecker.instance.rebuildTscDiagnostics(tscStrictDiagnostics); 120 const linter = !options.interopCheckMode ? 121 new TypeScriptLinter(tsProgram.getTypeChecker(), options, tscStrictDiagnostics) : 122 new InteropTypescriptLinter(tsProgram.getTypeChecker(), tsProgram.getCompilerOptions(), options, etsLoaderPath); 123 const { errorNodes, problemsInfos } = lintFiles(srcFiles, linter); 124 LibraryTypeCallDiagnosticChecker.instance.clear(); 125 consoleLog(options, '\n\n\nFiles scanned: ', srcFiles.length); 126 consoleLog(options, '\nFiles with problems: ', errorNodes); 127 128 const [errorNodesTotal, warningNodes] = countProblems(linter); 129 logTotalProblemsInfo(errorNodesTotal, warningNodes, linter); 130 logProblemsPercentageByFeatures(linter); 131 132 freeMemory(); 133 134 return { 135 errorNodes: errorNodesTotal, 136 problemsInfos: mergeArrayMaps(problemsInfos, transformTscDiagnostics(tscStrictDiagnostics)) 137 }; 138} 139 140function lintFiles(srcFiles: ts.SourceFile[], linter: TypeScriptLinter | InteropTypescriptLinter): LintRunResult { 141 let problemFiles = 0; 142 const problemsInfos: Map<string, ProblemInfo[]> = new Map(); 143 144 for (const srcFile of srcFiles) { 145 const prevVisitedNodes = linter.totalVisitedNodes; 146 const prevErrorLines = linter.totalErrorLines; 147 const prevWarningLines = linter.totalWarningLines; 148 linter.errorLineNumbersString = ''; 149 linter.warningLineNumbersString = ''; 150 const nodeCounters: number[] = []; 151 152 for (let i = 0; i < FaultID.LAST_ID; i++) { 153 nodeCounters[i] = linter.nodeCounters[i]; 154 } 155 156 linter.lint(srcFile); 157 // save results and clear problems array 158 problemsInfos.set(path.normalize(srcFile.fileName), [...linter.problemsInfos]); 159 linter.problemsInfos.length = 0; 160 161 // print results for current file 162 const fileVisitedNodes = linter.totalVisitedNodes - prevVisitedNodes; 163 const fileErrorLines = linter.totalErrorLines - prevErrorLines; 164 const fileWarningLines = linter.totalWarningLines - prevWarningLines; 165 166 problemFiles = countProblemFiles( 167 nodeCounters, 168 problemFiles, 169 srcFile, 170 fileVisitedNodes, 171 fileErrorLines, 172 fileWarningLines, 173 linter 174 ); 175 } 176 177 return { 178 errorNodes: problemFiles, 179 problemsInfos: problemsInfos 180 }; 181} 182 183// eslint-disable-next-line max-lines-per-function, max-params 184function countProblemFiles( 185 nodeCounters: number[], 186 filesNumber: number, 187 tsSrcFile: ts.SourceFile, 188 fileNodes: number, 189 fileErrorLines: number, 190 fileWarningLines: number, 191 linter: TypeScriptLinter | InteropTypescriptLinter 192): number { 193 let errorNodes = 0; 194 let warningNodes = 0; 195 for (let i = 0; i < FaultID.LAST_ID; i++) { 196 const nodeCounterDiff = linter.nodeCounters[i] - nodeCounters[i]; 197 switch (faultsAttrs[i].severity) { 198 case ProblemSeverity.ERROR: 199 errorNodes += nodeCounterDiff; 200 break; 201 case ProblemSeverity.WARNING: 202 warningNodes += nodeCounterDiff; 203 break; 204 default: 205 } 206 } 207 if (errorNodes > 0) { 208 // eslint-disable-next-line no-param-reassign 209 filesNumber++; 210 const errorRate = (errorNodes / fileNodes * 100).toFixed(2); 211 const warningRate = (warningNodes / fileNodes * 100).toFixed(2); 212 consoleLog(linter.options, tsSrcFile.fileName, ': ', '\n\tError lines: ', linter.errorLineNumbersString); 213 consoleLog(linter.options, tsSrcFile.fileName, ': ', '\n\tWarning lines: ', linter.warningLineNumbersString); 214 consoleLog( 215 linter.options, 216 '\n\tError constructs (%): ', 217 errorRate, 218 '\t[ of ', 219 fileNodes, 220 ' constructs ], \t', 221 fileErrorLines, 222 ' lines' 223 ); 224 consoleLog( 225 linter.options, 226 '\n\tWarning constructs (%): ', 227 warningRate, 228 '\t[ of ', 229 fileNodes, 230 ' constructs ], \t', 231 fileWarningLines, 232 ' lines' 233 ); 234 } 235 236 return filesNumber; 237} 238 239function logTotalProblemsInfo( 240 errorNodes: number, 241 warningNodes: number, 242 linter: TypeScriptLinter | InteropTypescriptLinter 243): void { 244 const errorRate = (errorNodes / linter.totalVisitedNodes * 100).toFixed(2); 245 const warningRate = (warningNodes / linter.totalVisitedNodes * 100).toFixed(2); 246 consoleLog(linter.options, '\nTotal error constructs (%): ', errorRate); 247 consoleLog(linter.options, '\nTotal warning constructs (%): ', warningRate); 248 consoleLog(linter.options, '\nTotal error lines:', linter.totalErrorLines, ' lines\n'); 249 consoleLog(linter.options, '\nTotal warning lines:', linter.totalWarningLines, ' lines\n'); 250} 251 252function logProblemsPercentageByFeatures(linter: TypeScriptLinter | InteropTypescriptLinter): void { 253 consoleLog(linter.options, '\nPercent by features: '); 254 for (let i = 0; i < FaultID.LAST_ID; i++) { 255 const nodes = linter.nodeCounters[i]; 256 const lines = linter.lineCounters[i]; 257 const pecentage = (nodes / linter.totalVisitedNodes * 100).toFixed(2).padEnd(7, ' '); 258 259 consoleLog(linter.options, faultDesc[i].padEnd(55, ' '), pecentage, '[', nodes, ' constructs / ', lines, ' lines]'); 260 } 261} 262 263function shouldProcessFile(options: LinterOptions, fileFsPath: string): boolean { 264 if ( 265 ARKTS_IGNORE_FILES.some((ignore) => { 266 return path.basename(fileFsPath) === ignore; 267 }) 268 ) { 269 return false; 270 } 271 272 if ( 273 ARKTS_IGNORE_DIRS_NO_OH_MODULES.some((ignore) => { 274 return pathContainsDirectory(path.resolve(fileFsPath), ignore); 275 }) 276 ) { 277 return false; 278 } 279 280 return ( 281 !pathContainsDirectory(path.resolve(fileFsPath), ARKTS_IGNORE_DIRS_OH_MODULES) || 282 !!options.isFileFromModuleCb?.(fileFsPath) 283 ); 284} 285 286function freeMemory(): void { 287 clearPathHelperCache(); 288} 289