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