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