diff --git a/ets2panda/linter/src/cli/CommandLineParser.ts b/ets2panda/linter/src/cli/CommandLineParser.ts index 42e2cf404ba11366bcef40b993c018094f7d1d48..2d95691a15dd9137cb35a105a5e39bb21ad58480 100644 --- a/ets2panda/linter/src/cli/CommandLineParser.ts +++ b/ets2panda/linter/src/cli/CommandLineParser.ts @@ -13,15 +13,15 @@ * limitations under the License. */ -import { Logger } from '../lib/Logger'; -import { logTscDiagnostic } from '../lib/utils/functions/LogTscDiagnostic'; -import type { CommandLineOptions } from '../lib/CommandLineOptions'; -import { ARKTS_IGNORE_DIRS_OH_MODULES } from '../lib/utils/consts/ArktsIgnorePaths'; import type { OptionValues } from 'commander'; import { Command, Option } from 'commander'; -import * as ts from 'typescript'; import * as fs from 'node:fs'; import * as path from 'node:path'; +import * as ts from 'typescript'; +import type { CommandLineOptions } from '../lib/CommandLineOptions'; +import { Logger } from '../lib/Logger'; +import { ARKTS_IGNORE_DIRS_OH_MODULES } from '../lib/utils/consts/ArktsIgnorePaths'; +import { logTscDiagnostic } from '../lib/utils/functions/LogTscDiagnostic'; const TS_EXT = '.ts'; const TSX_EXT = '.tsx'; @@ -179,6 +179,7 @@ function formCommandLineOptions(parsedCmd: ParsedCommand): CommandLineOptions { } if (options.projectFolder) { doProjectFolderArg(options.projectFolder, opts); + opts.linterOptions.projectFolderList = options.projectFolder; } if (options.project) { doProjectArg(options.project, opts); diff --git a/ets2panda/linter/src/cli/LinterCLI.ts b/ets2panda/linter/src/cli/LinterCLI.ts index 438c56756ca19db15cabd61c67cad9b733ce1c98..17edbb9b4bfa47603f7c87bb50e17f9a9e363404 100644 --- a/ets2panda/linter/src/cli/LinterCLI.ts +++ b/ets2panda/linter/src/cli/LinterCLI.ts @@ -13,20 +13,24 @@ * limitations under the License. */ +import { MigrationTool } from 'homecheck'; import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; import * as readline from 'node:readline'; import type { CommandLineOptions } from '../lib/CommandLineOptions'; +import { getHomeCheckConfigInfo, transferIssues2ProblemInfo } from '../lib/HomeCheck'; import { lint } from '../lib/LinterRunner'; import { Logger } from '../lib/Logger'; import type { ProblemInfo } from '../lib/ProblemInfo'; -import { parseCommandLine } from './CommandLineParser'; -import { compileLintOptions, getEtsLoaderPath } from '../lib/ts-compiler/Compiler'; +import * as statistic from '../lib/statistics/scan/ProblemStatisticsCommonFunction'; +import type { ScanTaskRelatedInfo } from '../lib/statistics/scan/ScanTaskRelatedInfo'; +import { StatisticsReportInPutInfo } from '../lib/statistics/scan/StatisticsReportInPutInfo'; +import { TimeRecorder } from '../lib/statistics/scan/TimeRecorder'; import { logStatistics } from '../lib/statistics/StatisticsLogger'; -import { MigrationTool } from 'homecheck'; -import { getHomeCheckConfigInfo, transferIssues2ProblemInfo } from '../lib/HomeCheck'; +import { compileLintOptions, getEtsLoaderPath } from '../lib/ts-compiler/Compiler'; import { processSyncErr, processSyncOut } from '../lib/utils/functions/ProcessWrite'; +import { parseCommandLine } from './CommandLineParser'; export function run(): void { const commandLineArgs = process.argv.slice(2); @@ -43,7 +47,7 @@ export function run(): void { runIdeInteractiveMode(cmdOptions); } else { const compileOptions = compileLintOptions(cmdOptions); - const result = lint(compileOptions); + const result = lint(compileOptions, new TimeRecorder()); logStatistics(result.projectStats); process.exit(result.hasErrors ? 1 : 0); } @@ -52,36 +56,28 @@ export function run(): void { async function runIdeInteractiveMode(cmdOptions: CommandLineOptions): Promise { cmdOptions.followSdkSettings = true; cmdOptions.disableStrictDiagnostics = true; + const timeRecorder = new TimeRecorder(); + const scanTaskRelatedInfo = {} as ScanTaskRelatedInfo; const compileOptions = compileLintOptions(cmdOptions); - let homeCheckResult = new Map(); - const mergedProblems = new Map(); - - if (cmdOptions.linterOptions.arkts2 && cmdOptions.homecheck) { - const { ruleConfigInfo, projectConfigInfo } = getHomeCheckConfigInfo(cmdOptions); - let migrationTool: MigrationTool | null = new MigrationTool(ruleConfigInfo, projectConfigInfo); - await migrationTool.buildCheckEntry(); - const result = await migrationTool.start(); - migrationTool = null; - - homeCheckResult = transferIssues2ProblemInfo(result); - for (const [filePath, problems] of homeCheckResult) { - if (!mergedProblems.has(filePath)) { - mergedProblems.set(filePath, []); - } - mergedProblems.get(filePath)!.push(...problems); - } - } - - if (!cmdOptions.skipLinter) { - const result = lint(compileOptions, getEtsLoaderPath(compileOptions), homeCheckResult); - for (const [filePath, problems] of result.problemsInfos) { - mergeLintProblems(filePath, problems, mergedProblems, cmdOptions); - } + scanTaskRelatedInfo.cmdOptions = cmdOptions; + scanTaskRelatedInfo.timeRecorder = timeRecorder; + scanTaskRelatedInfo.compileOptions = compileOptions; + await executeScanTask(scanTaskRelatedInfo); + + const statisticsReportInPutInfo = scanTaskRelatedInfo.statisticsReportInPutInfo; + statisticsReportInPutInfo.statisticsReportName = 'scan-problems-statistics.json'; + statisticsReportInPutInfo.totalProblemNumbers = getTotalProblemNumbers(scanTaskRelatedInfo.mergedProblems); + statisticsReportInPutInfo.cmdOptions = cmdOptions; + statisticsReportInPutInfo.timeRecorder = timeRecorder; + + if (!cmdOptions.linterOptions.migratorMode && statisticsReportInPutInfo.cmdOptions.linterOptions.projectFolderList) { + await statistic.generateScanProbelemStatisticsReport(statisticsReportInPutInfo); } + const mergedProblems = scanTaskRelatedInfo.mergedProblems; const reportData = Object.fromEntries(mergedProblems); - await generateReportFile(reportData, cmdOptions.outputFilePath); - + const reportName: string = 'scan-report.json'; + await statistic.generateReportFile(reportName, reportData, cmdOptions.outputFilePath); for (const [filePath, problems] of mergedProblems) { const reportLine = JSON.stringify({ filePath, problems }) + '\n'; await processSyncOut(reportLine); @@ -90,6 +86,79 @@ async function runIdeInteractiveMode(cmdOptions: CommandLineOptions): Promise): number { + let totalProblemNumbers: number = 0; + for (const problems of mergedProblems.values()) { + totalProblemNumbers += problems.length; + } + return totalProblemNumbers; +} + +async function executeScanTask(scanTaskRelatedInfo: ScanTaskRelatedInfo): Promise { + const cmdOptions = scanTaskRelatedInfo.cmdOptions; + scanTaskRelatedInfo.statisticsReportInPutInfo = new StatisticsReportInPutInfo(); + scanTaskRelatedInfo.statisticsReportInPutInfo.ruleToNumbersMap = new Map(); + scanTaskRelatedInfo.statisticsReportInPutInfo.ruleToAutoFixedNumbersMap = new Map(); + scanTaskRelatedInfo.mergedProblems = new Map(); + if (cmdOptions.linterOptions.arkts2 && cmdOptions.homecheck) { + await executeHomeCheckTask(scanTaskRelatedInfo); + } + + if (!cmdOptions.skipLinter) { + executeLintTask(scanTaskRelatedInfo); + } +} + +async function executeHomeCheckTask(scanTaskRelatedInfo: ScanTaskRelatedInfo): Promise { + const cmdOptions = scanTaskRelatedInfo.cmdOptions; + const { ruleConfigInfo, projectConfigInfo } = getHomeCheckConfigInfo(cmdOptions); + let migrationTool: MigrationTool | null = new MigrationTool(ruleConfigInfo, projectConfigInfo); + await migrationTool.buildCheckEntry(); + scanTaskRelatedInfo.timeRecorder.startScan(); + scanTaskRelatedInfo.timeRecorder.setHomeCheckCountStatus(true); + const result = await migrationTool.start(); + migrationTool = null; + scanTaskRelatedInfo.homeCheckResult = transferIssues2ProblemInfo(result); + for (const [filePath, problems] of scanTaskRelatedInfo.homeCheckResult) { + if (!scanTaskRelatedInfo.mergedProblems.has(filePath)) { + scanTaskRelatedInfo.mergedProblems.set(filePath, []); + } + statistic.accumulateRuleNumbers( + problems, + scanTaskRelatedInfo.statisticsReportInPutInfo.ruleToNumbersMap, + scanTaskRelatedInfo.statisticsReportInPutInfo.ruleToAutoFixedNumbersMap + ); + scanTaskRelatedInfo.statisticsReportInPutInfo.arkOnePointOneProblemNumbers += + statistic.getArktsOnePointOneProlemNumbers(problems); + scanTaskRelatedInfo.mergedProblems.get(filePath)!.push(...problems); + } +} + +function executeLintTask(scanTaskRelatedInfo: ScanTaskRelatedInfo): void { + const cmdOptions = scanTaskRelatedInfo.cmdOptions; + const compileOptions = scanTaskRelatedInfo.compileOptions; + const homeCheckResult = scanTaskRelatedInfo.homeCheckResult; + if (!scanTaskRelatedInfo.timeRecorder.getHomeCheckCountStatus()) { + scanTaskRelatedInfo.timeRecorder.startScan(); + } + const result = lint( + compileOptions, + scanTaskRelatedInfo.timeRecorder, + getEtsLoaderPath(compileOptions), + homeCheckResult + ); + for (const [filePath, problems] of result.problemsInfos) { + statistic.accumulateRuleNumbers( + problems, + scanTaskRelatedInfo.statisticsReportInPutInfo.ruleToNumbersMap, + scanTaskRelatedInfo.statisticsReportInPutInfo.ruleToAutoFixedNumbersMap + ); + scanTaskRelatedInfo.statisticsReportInPutInfo.arkOnePointOneProblemNumbers += + statistic.getArktsOnePointOneProlemNumbers(problems); + mergeLintProblems(filePath, problems, scanTaskRelatedInfo.mergedProblems, cmdOptions); + } +} + function mergeLintProblems( filePath: string, problems: ProblemInfo[], @@ -123,19 +192,6 @@ function mergeLintProblems( } } -async function generateReportFile(reportData, reportPath?: string): Promise { - let reportFilePath = path.join('scan-report.json'); - if (reportPath !== undefined) { - reportFilePath = path.join(path.normalize(reportPath), 'scan-report.json'); - } - try { - await fs.promises.mkdir(path.dirname(reportFilePath), { recursive: true }); - await fs.promises.writeFile(reportFilePath, JSON.stringify(reportData, null, 2)); - } catch (error) { - console.error('Error generating report file:', error); - } -} - function getTempFileName(): string { return path.join(os.tmpdir(), Math.floor(Math.random() * 10000000).toString() + '_linter_tmp_file.ts'); } @@ -178,7 +234,7 @@ function runIdeModeDeprecated(cmdOptions: CommandLineOptions): void { cmdOptions.parsedConfigFile.fileNames.push(tmpFileName); } const compileOptions = compileLintOptions(cmdOptions); - const result = lint(compileOptions); + const result = lint(compileOptions, new TimeRecorder()); const problems = Array.from(result.problemsInfos.values()); if (problems.length === 1) { showJSONMessage(problems); diff --git a/ets2panda/linter/src/lib/LintRunResult.ts b/ets2panda/linter/src/lib/LintRunResult.ts index 3397a3941a433ce685b7daca2e52ca52e2b2d678..9c1519d077c3a1140e0aa1df077ae0a1d205ea7f 100644 --- a/ets2panda/linter/src/lib/LintRunResult.ts +++ b/ets2panda/linter/src/lib/LintRunResult.ts @@ -15,9 +15,11 @@ import type { ProblemInfo } from './ProblemInfo'; import type { ProjectStatistics } from './statistics/ProjectStatistics'; +import type { TimeRecorder } from './statistics/scan/TimeRecorder'; export interface LintRunResult { hasErrors: boolean; problemsInfos: Map; projectStats: ProjectStatistics; + timeRecorder?: TimeRecorder; } diff --git a/ets2panda/linter/src/lib/LinterOptions.ts b/ets2panda/linter/src/lib/LinterOptions.ts index 11c2183d5cf0d96c5adc7b4362c71a7edf126bc2..af9a0c29030e947818ab3ea5454f84b50c25167a 100644 --- a/ets2panda/linter/src/lib/LinterOptions.ts +++ b/ets2panda/linter/src/lib/LinterOptions.ts @@ -44,6 +44,7 @@ export interface LinterOptions { noMigrationBackupFile?: boolean; migrationReport?: boolean; wholeProjectPath?: string; + projectFolderList?: string[]; checkTsAndJs?: boolean; inputFiles?: string[]; onlySyntax?: boolean; diff --git a/ets2panda/linter/src/lib/LinterRunner.ts b/ets2panda/linter/src/lib/LinterRunner.ts index ea35b6ea9e42d12064ca19715610fe91168a648c..863687d5541c6bcfdc0a47fbf3eb63d6daeba752 100644 --- a/ets2panda/linter/src/lib/LinterRunner.ts +++ b/ets2panda/linter/src/lib/LinterRunner.ts @@ -16,6 +16,9 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; import * as ts from 'typescript'; +import { processSyncErr } from '../lib/utils/functions/ProcessWrite'; +import * as qEd from './autofixes/QuasiEditor'; +import type { BaseTypeScriptLinter } from './BaseTypeScriptLinter'; import type { CommandLineOptions } from './CommandLineOptions'; import { InteropTypescriptLinter } from './InteropTypescriptLinter'; import type { LinterConfig } from './LinterConfig'; @@ -23,25 +26,24 @@ import type { LinterOptions } from './LinterOptions'; import type { LintRunResult } from './LintRunResult'; import { Logger } from './Logger'; import type { ProblemInfo } from './ProblemInfo'; -import { TypeScriptLinter } from './TypeScriptLinter'; +import { ProjectStatistics } from './statistics/ProjectStatistics'; +import { generateMigrationStatisicsReport } from './statistics/scan/ProblemStatisticsCommonFunction'; +import type { TimeRecorder } from './statistics/scan/TimeRecorder'; +import type { createProgramCallback } from './ts-compiler/Compiler'; +import { compileLintOptions } from './ts-compiler/Compiler'; import { getTscDiagnostics } from './ts-diagnostics/GetTscDiagnostics'; import { transformTscDiagnostics } from './ts-diagnostics/TransformTscDiagnostics'; +import { TypeScriptLinter } from './TypeScriptLinter'; import { ARKTS_IGNORE_DIRS_NO_OH_MODULES, ARKTS_IGNORE_DIRS_OH_MODULES, ARKTS_IGNORE_FILES } from './utils/consts/ArktsIgnorePaths'; +import { EXTNAME_JS, EXTNAME_TS } from './utils/consts/ExtensionName'; import { USE_STATIC } from './utils/consts/InteropAPI'; -import { EXTNAME_TS, EXTNAME_JS } from './utils/consts/ExtensionName'; +import { LibraryTypeCallDiagnosticChecker } from './utils/functions/LibraryTypeCallDiagnosticChecker'; import { mergeArrayMaps } from './utils/functions/MergeArrayMaps'; import { clearPathHelperCache, pathContainsDirectory } from './utils/functions/PathHelper'; -import { LibraryTypeCallDiagnosticChecker } from './utils/functions/LibraryTypeCallDiagnosticChecker'; -import type { createProgramCallback } from './ts-compiler/Compiler'; -import { compileLintOptions } from './ts-compiler/Compiler'; -import * as qEd from './autofixes/QuasiEditor'; -import { ProjectStatistics } from './statistics/ProjectStatistics'; -import type { BaseTypeScriptLinter } from './BaseTypeScriptLinter'; -import { processSyncErr } from '../lib/utils/functions/ProcessWrite'; function prepareInputFilesList(cmdOptions: CommandLineOptions): string[] { let inputFiles = cmdOptions.inputFiles.map((x) => { @@ -77,6 +79,7 @@ function prepareInputFilesList(cmdOptions: CommandLineOptions): string[] { export function lint( config: LinterConfig, + timeRecorder: TimeRecorder, etsLoaderPath?: string, hcResults?: Map ): LintRunResult { @@ -84,7 +87,10 @@ export function lint( config.cmdOptions.linterOptions.etsLoaderPath = etsLoaderPath; } const lintResult = lintImpl(config); - return config.cmdOptions.linterOptions.migratorMode ? migrate(config, lintResult, hcResults) : lintResult; + timeRecorder.endScan(); + return config.cmdOptions.linterOptions.migratorMode ? + migrate(config, lintResult, timeRecorder, hcResults) : + lintResult; } function lintImpl(config: LinterConfig): LintRunResult { @@ -156,8 +162,10 @@ function lintFiles( function migrate( initialConfig: LinterConfig, initialLintResult: LintRunResult, + timeRecorder: TimeRecorder, hcResults?: Map ): LintRunResult { + timeRecorder.startMigration(); let linterConfig = initialConfig; const { cmdOptions } = initialConfig; const updatedSourceTexts: Map = new Map(); @@ -188,6 +196,9 @@ function migrate( fs.writeFileSync(writeFileName, newText); }); + timeRecorder.endMigration(); + generateMigrationStatisicsReport(lintResult, timeRecorder, cmdOptions.outputFilePath); + if (cmdOptions.linterOptions.ideInteractive) { lintResult.problemsInfos = problemsInfosBeforeMigrate; } diff --git a/ets2panda/linter/src/lib/statistics/scan/CountFile.ts b/ets2panda/linter/src/lib/statistics/scan/CountFile.ts new file mode 100644 index 0000000000000000000000000000000000000000..04f0166f357d2edf02567784d0c2662214963efb --- /dev/null +++ b/ets2panda/linter/src/lib/statistics/scan/CountFile.ts @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import fs from 'fs'; +import path from 'path'; +import { promisify } from 'util'; + +// Define file type extensions +const FILE_TYPES: { [key: string]: string[] } = { + 'C/C++': ['.c', '.h', '.cpp', '.hpp'], + JavaScript: ['.js'], + TypeScript: ['.ts'], + JSON: ['.json', '.json5'], + XML: ['.xml'], + ArkTS: ['.ets'], + 'ArkTS Test': ['.test.ets'] +}; + +// Comment regex patterns by file type +const COMMENT_REGEX1: { [key: string]: RegExp } = { + 'C/C++': /\/\/.*/g, + JavaScript: /\/\/.*/g, + TypeScript: /\/\/.*/g, + ArkTS: /\/\/.*/g, + 'ArkTS Test': /\/\/.*/g, + XML: //g +}; + +const COMMENT_REGEX2: { [key: string]: RegExp } = { + 'C/C++': /\/\*.*?\*\//gs, + JavaScript: /\/\*.*?\*\//gs, + TypeScript: /\/\*.*?\*\//gs, + ArkTS: /\/\*.*?\*\//gs, + 'ArkTS Test': /\/\*.*?\*\//gs +}; + +/** + * Remove comments from file content + */ +function removeComments(content: string, fileType: string): string { + if (COMMENT_REGEX1[fileType]) { + content = content.replace(COMMENT_REGEX1[fileType], ''); + } + if (COMMENT_REGEX2[fileType]) { + content = content.replace(COMMENT_REGEX2[fileType], ''); + } + return content; +} + +/** + * Count valid lines of code (excluding comments and empty lines) + */ +async function countLines(filePath: string, fileType: string): Promise { + try { + const content = await promisify(fs.readFile)(filePath, 'utf-8'); + const cleanedContent = removeComments(content, fileType); + const lines = cleanedContent.split('\n'); + + // Filter out empty lines + const validLines = lines.filter((line) => { + return line.trim(); + }); + return validLines.length; + } catch (error) { + console.error(`Error reading ${filePath}: ${error}`); + return 0; + } +} + +/** + * Merge multiple result objects + */ +function mergeAllResults(results: { [key: string]: { fileCount: number; lineCount: number } }[]): { + [key: string]: { fileCount: number; lineCount: number }; +} { + const combined: { [key: string]: { fileCount: number; lineCount: number } } = {}; + + for (const result of results) { + for (const [type, counts] of Object.entries(result)) { + if (!combined[type]) { + combined[type] = { fileCount: 0, lineCount: 0 }; + } + combined[type].fileCount += counts.fileCount; + combined[type].lineCount += counts.lineCount; + } + } + + return combined; +} + +/** + * Process directory entries recursively + */ +async function walkDir(dir: string): Promise<{ [key: string]: { fileCount: number; lineCount: number } }> { + try { + const entries = await promisify(fs.readdir)(dir, { withFileTypes: true }); + const fileResults = await processFiles(dir, entries); + const dirResults = await processDirs(dir, entries); + return mergeAllResults([fileResults, dirResults]); + } catch (error) { + console.error(`Error reading ${dir}: ${error}`); + return {}; + } +} + +/** + * Process files in a directory + */ +async function processFiles( + dir: string, + entries: fs.Dirent[] +): Promise<{ [key: string]: { fileCount: number; lineCount: number } }> { + const fileResults = await Promise.all( + entries. + filter((entry) => { + return entry.isFile(); + }). + map(async(entry) => { + return processFileEntry(dir, entry); + }) + ); + return mergeAllResults(fileResults); +} + +/** + * Process a single file entry + */ +async function processFileEntry( + dir: string, + entry: fs.Dirent +): Promise<{ [key: string]: { fileCount: number; lineCount: number } }> { + const fullPath = path.join(dir, entry.name); + + for (const fileType in FILE_TYPES) { + const extensions = FILE_TYPES[fileType]; + const ext = path.extname(entry.name); + + if (extensions.includes(ext)) { + const lines = await countLines(fullPath, fileType); + return { + [fileType]: { + fileCount: 1, + lineCount: lines + } + }; + } + } + + return {}; +} + +/** + * Process subdirectories recursively + */ +async function processDirs( + dir: string, + entries: fs.Dirent[] +): Promise<{ [key: string]: { fileCount: number; lineCount: number } }> { + const dirEntries = entries.filter((entry) => { + return entry.isDirectory(); + }); + const dirResults = await Promise.all( + dirEntries.map((entry) => { + return walkDir(path.join(dir, entry.name)); + }) + ); + return mergeAllResults(dirResults); +} + +/** + * Analyze directory and count files/lines by type + */ +async function analyzeDirectory( + directory: string +): Promise<{ [key: string]: { fileCount: number; lineCount: number } }> { + // Initialize results + const results: { [key: string]: { fileCount: number; lineCount: number } } = {}; + for (const fileType in FILE_TYPES) { + results[fileType] = { fileCount: 0, lineCount: 0 }; + } + + const finalResults = await walkDir(directory); + Object.assign(results, finalResults); + return results; +} + +/** + * Main export function + */ +export async function countFiles( + directory: string +): Promise<{ [key: string]: { fileCount: number; lineCount: number } }> { + try { + const stats = await promisify(fs.stat)(directory); + if (stats.isDirectory()) { + return await analyzeDirectory(directory); + } + console.error('The directory is invalid!'); + return {}; + } catch (error) { + console.error('Read directory failed', error); + return {}; + } +} diff --git a/ets2panda/linter/src/lib/statistics/scan/CountNapiFile.ts b/ets2panda/linter/src/lib/statistics/scan/CountNapiFile.ts new file mode 100644 index 0000000000000000000000000000000000000000..93e31a0aa5c361c1c4cafb684a7ba3056849d837 --- /dev/null +++ b/ets2panda/linter/src/lib/statistics/scan/CountNapiFile.ts @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as fs from 'fs'; +import * as path from 'path'; +import type { NapiFileStatisticInfo } from './NapiFileStatisticInfo'; + +const EXTENSIONS = ['.c', '.cpp', '.cc', '.cxx', '.h', '.hpp', '.hh', '.hxx']; + +const SINGLE_LINE_COMMENT_REGEX = /\/\/.*/g; +const MULTI_LINE_COMMENT_REGEX = /\/\*[\s\S]*?\*\//g; + +const DEFAULT_STATISTICS: NapiFileStatisticInfo = { + totalFiles: 0, + totalLines: 0, + napiFiles: 0, + napiLines: 0, + napiFileLines: 0 +}; + +function removeComments(content: string): string { + return content.replace(MULTI_LINE_COMMENT_REGEX, '').replace(SINGLE_LINE_COMMENT_REGEX, ''); +} + +async function countLines(filePath: string): Promise { + try { + const content = await fs.promises.readFile(filePath, 'utf-8'); + const contentWithoutComments = removeComments(content); + const validLines = contentWithoutComments.split('\n').filter((line) => { + return line.trim(); + }); + return validLines.length; + } catch (e) { + console.error(`Error reading ${filePath}: ${e}`); + return 0; + } +} + +async function countNapiLines(filePath: string): Promise { + try { + const content = await fs.promises.readFile(filePath, 'utf-8'); + const lines = content.split('\n'); + const napiLines = new Set(); + + for (const line of lines) { + if (line.toLowerCase().includes('napi')) { + napiLines.add(line); + } + } + + return napiLines.size; + } catch (e) { + console.error(`Error reading ${filePath}: ${e}`); + return 0; + } +} + +async function analyzeDirectoryAsync(directory: string): Promise { + const dirQueue: string[] = [directory]; + const allResults: NapiFileStatisticInfo[] = []; + + while (dirQueue.length > 0) { + const currentDir = dirQueue.shift()!; + const entries = await fs.promises.readdir(currentDir, { withFileTypes: true }); + const fileResults = await Promise.all( + entries. + map((entry) => { + const fullPath = path.join(currentDir, entry.name); + if (entry.isDirectory()) { + dirQueue.push(fullPath); + return null; + } else if (isTargetFile(entry.name)) { + return processFile(fullPath); + } + return null; + }). + filter(Boolean) as Promise[] + ); + allResults.push(...fileResults); + } + + return allResults.reduce( + (acc, cur) => { + acc.totalFiles += cur.totalFiles; + acc.totalLines += cur.totalLines; + if (cur.napiFiles > 0) { + acc.napiFiles += cur.napiFiles; + acc.napiLines += cur.napiLines; + acc.napiFileLines += cur.napiFileLines; + } + return acc; + }, + { ...DEFAULT_STATISTICS } + ); +} + +async function processFile(filePath: string): Promise { + const result: NapiFileStatisticInfo = { + totalFiles: 1, + totalLines: 0, + napiFiles: 0, + napiLines: 0, + napiFileLines: 0 + }; + + try { + const [lines, napiCount] = await Promise.all([countLines(filePath), countNapiLines(filePath)]); + + result.totalLines = lines; + if (napiCount > 0) { + result.napiFiles = 1; + result.napiLines = napiCount; + result.napiFileLines = lines; + } + } catch (e) { + console.error(`Error processing ${filePath}: ${e}`); + } + return result; +} + +function isTargetFile(filename: string): boolean { + return EXTENSIONS.some((ext) => { + return filename.endsWith(ext); + }); +} + +export async function countNapiFiles(directory: string): Promise { + try { + const stat = await fs.promises.stat(directory); + if (!stat.isDirectory()) { + console.log('The provided path is not a directory!'); + return DEFAULT_STATISTICS; + } + return await analyzeDirectoryAsync(directory); + } catch (e) { + console.error(`Error accessing directory ${directory}: ${e}`); + return DEFAULT_STATISTICS; + } +} diff --git a/ets2panda/linter/src/lib/statistics/scan/FloderScanResultInfo.ts b/ets2panda/linter/src/lib/statistics/scan/FloderScanResultInfo.ts new file mode 100644 index 0000000000000000000000000000000000000000..447813a2d2df7baf6b55caf1056c8a5231f27a0e --- /dev/null +++ b/ets2panda/linter/src/lib/statistics/scan/FloderScanResultInfo.ts @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface FloderScanResultInfo { + normalizedPath: string; + arkTSCodeLines: number; + cAndCPPCodeLines: number; + napiCodeLines: number; + jsCodeLines: number; + tsCodeLines: number; + jsonCodeLines: number; + xmlCodeLines: number; +} diff --git a/ets2panda/linter/src/lib/statistics/scan/NapiFileStatisticInfo.ts b/ets2panda/linter/src/lib/statistics/scan/NapiFileStatisticInfo.ts new file mode 100644 index 0000000000000000000000000000000000000000..20b324380215c2eb4c542e833a8d7eb2f7a8bffd --- /dev/null +++ b/ets2panda/linter/src/lib/statistics/scan/NapiFileStatisticInfo.ts @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface NapiFileStatisticInfo { + totalFiles: number; + totalLines: number; + napiFiles: number; + napiLines: number; + napiFileLines: number; +} diff --git a/ets2panda/linter/src/lib/statistics/scan/ProblemNumbersInfo.ts b/ets2panda/linter/src/lib/statistics/scan/ProblemNumbersInfo.ts new file mode 100644 index 0000000000000000000000000000000000000000..d969313546fc3883371a58401a0c74a87cf1ad9d --- /dev/null +++ b/ets2panda/linter/src/lib/statistics/scan/ProblemNumbersInfo.ts @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface ProblemNumbersInfo { + totalProblemNumbers: number; + arkOnePointOneProblemNumbers: number; + arkOnePointTWOProblemNumbers: number; + canBeAutoFixedproblemNumbers: number; + needToManualFixproblemNumbers: number; +} diff --git a/ets2panda/linter/src/lib/statistics/scan/ProblemStatisticsCommonFunction.ts b/ets2panda/linter/src/lib/statistics/scan/ProblemStatisticsCommonFunction.ts new file mode 100644 index 0000000000000000000000000000000000000000..cdf625ab5fc03cd866ed8e1afc375ed728e5b2f2 --- /dev/null +++ b/ets2panda/linter/src/lib/statistics/scan/ProblemStatisticsCommonFunction.ts @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import type { LintRunResult } from '../../LintRunResult'; +import type { ProblemInfo } from '../../ProblemInfo'; +import * as mk from '../../utils/consts/MapKeyConst'; +import { countFiles } from './CountFile'; +import { countNapiFiles } from './CountNapiFile'; +import type { ProblemNumbersInfo } from './ProblemNumbersInfo'; +import type { ProblemStatisticsInfo } from './ProblemStatisticsInfo'; +import type { RuleDetailedErrorInfo } from './RuleDetailedErrorInfo'; +import type { StatisticsReportInPutInfo } from './StatisticsReportInPutInfo'; +import type { TimeRecorder } from './TimeRecorder'; +import { WorkLoadInfo } from './WorkLoadInfo'; + +export function getProblemStatisticsInfo( + problemNumbers: ProblemNumbersInfo, + ruleToNumbersMap: Map, + ruleToAutoFixedNumbersMap: Map, + timeRecorder: TimeRecorder, + WorkLoadInfo?: WorkLoadInfo +): ProblemStatisticsInfo { + const problemNumberMap: Map = new Map(); + problemNumberMap.set(mk.TOTAL_PROBLEMS, problemNumbers.totalProblemNumbers); + problemNumberMap.set(mk.ONE_POINT_ONE_PROBLEMS, problemNumbers.arkOnePointOneProblemNumbers); + problemNumberMap.set(mk.ONE_POINT_TWO_PROBLEMS, problemNumbers.arkOnePointTWOProblemNumbers); + problemNumberMap.set(mk.CAN_BE_AUTO_FIXED_PROBLEMS_NUMBERS, problemNumbers.canBeAutoFixedproblemNumbers); + problemNumberMap.set(mk.NEED_TO_NAMUAL_FIX_PROBLEM_NUMBERS, problemNumbers.needToManualFixproblemNumbers); + + const scanTime = timeRecorder.getScanTime(); + const migrationTime = timeRecorder.getMigrationTime(); + const usedTimeMap: Map = new Map(); + usedTimeMap.set(mk.SCAN_TIME, scanTime); + usedTimeMap.set(mk.MIGRATION_TIME, migrationTime); + + const detailRuleProblemsMap: Map = new Map(); + ruleToNumbersMap.forEach((value, key) => { + const ruleDetailedErrorInfo: RuleDetailedErrorInfo = { + rule: key, + problemNumbers: value, + canBeAutoFixedMumbers: ruleToAutoFixedNumbersMap.get(key) ?? 0 + }; + detailRuleProblemsMap.set(key, ruleDetailedErrorInfo); + }); + + const problemStatisticsInfo: ProblemStatisticsInfo = { + problems: Object.fromEntries(problemNumberMap), + usedTime: Object.fromEntries(usedTimeMap), + WorkLoadInfo: WorkLoadInfo, + eachRuleProblemsDetail: Array.from(detailRuleProblemsMap.values()) + }; + return problemStatisticsInfo; +} + +export function getArktsOnePointOneProlemNumbers(problems: ProblemInfo[]): number { + let problemNumbersForATSOnePointOne: number = 0; + const signForOne: string = 's2d'; + problems.forEach((problem) => { + if (problem.rule !== undefined) { + if (problem.rule.includes(signForOne)) { + problemNumbersForATSOnePointOne = problemNumbersForATSOnePointOne + 1; + } + } + }); + return problemNumbersForATSOnePointOne; +} + +export function accumulateRuleNumbers( + problems: ProblemInfo[], + ruleToNumbersMap: Map, + ruleToAutoFixedNumbersMap: Map +): void { + problems.forEach((problem) => { + if (problem.rule !== undefined) { + if (problem.autofix) { + const currentNumber = ruleToAutoFixedNumbersMap.get(problem.rule) || 0; + ruleToAutoFixedNumbersMap.set(problem.rule, currentNumber + 1); + } + const currentNumber = ruleToNumbersMap.get(problem.rule) || 0; + ruleToNumbersMap.set(problem.rule, currentNumber + 1); + } + }); +} + +export async function generateReportFile(reportName: string, reportData, reportPath?: string): Promise { + let reportFilePath = path.join(reportName); + if (reportPath !== undefined) { + reportFilePath = path.join(path.normalize(reportPath), reportName); + } + try { + await fs.promises.mkdir(path.dirname(reportFilePath), { recursive: true }); + await fs.promises.writeFile(reportFilePath, JSON.stringify(reportData, null, 2)); + } catch (error) { + console.error('Error generating report file:', error); + } +} + +export function generateReportFileSync(reportName: string, reportData: any, reportPath?: string): void { + let reportFilePath = reportName; + if (reportPath !== undefined) { + reportFilePath = path.join(path.normalize(reportPath), reportName); + } + try { + fs.mkdirSync(path.dirname(reportFilePath), { recursive: true }); + fs.writeFileSync(reportFilePath, JSON.stringify(reportData, null, 2)); + } catch (error) { + console.error('Error generating report file:', error); + } +} + +export function getMapValueSum(map: Map): number { + let result = 0; + for (const value of map.values()) { + result += value; + } + return result; +} + +export async function generateScanProbelemStatisticsReport( + statisticsReportInPutInfo: StatisticsReportInPutInfo +): Promise { + const canBeAutoFixedproblemNumbers = getMapValueSum(statisticsReportInPutInfo.ruleToAutoFixedNumbersMap); + const problemNumbers: ProblemNumbersInfo = { + totalProblemNumbers: statisticsReportInPutInfo.totalProblemNumbers, + arkOnePointOneProblemNumbers: statisticsReportInPutInfo.arkOnePointOneProblemNumbers, + arkOnePointTWOProblemNumbers: + statisticsReportInPutInfo.totalProblemNumbers - statisticsReportInPutInfo.arkOnePointOneProblemNumbers, + canBeAutoFixedproblemNumbers: canBeAutoFixedproblemNumbers, + needToManualFixproblemNumbers: statisticsReportInPutInfo.totalProblemNumbers - canBeAutoFixedproblemNumbers + }; + const projectFolderList = statisticsReportInPutInfo.cmdOptions.linterOptions.projectFolderList!; + const WorkLoadInfo = await getWorkLoadInfo(projectFolderList); + const statisticsReportData = getProblemStatisticsInfo( + problemNumbers, + statisticsReportInPutInfo.ruleToNumbersMap, + statisticsReportInPutInfo.ruleToAutoFixedNumbersMap, + statisticsReportInPutInfo.timeRecorder, + WorkLoadInfo + ); + await generateReportFile( + statisticsReportInPutInfo.statisticsReportName, + statisticsReportData, + statisticsReportInPutInfo.cmdOptions.outputFilePath + ); +} + +export function generateMigrationStatisicsReport( + lintResult: LintRunResult, + timeRecorder: TimeRecorder, + outputFilePath?: string +): void { + const ruleToNumbersMap: Map = new Map(); + const ruleToAutoFixedNumbersMap: Map = new Map(); + let arkOnePointOneProblemNumbers: number = 0; + + const problemsInfoAfterAutofixed = lintResult.problemsInfos; + for (const problems of problemsInfoAfterAutofixed.values()) { + accumulateRuleNumbers(problems, ruleToNumbersMap, ruleToAutoFixedNumbersMap); + arkOnePointOneProblemNumbers = arkOnePointOneProblemNumbers + getArktsOnePointOneProlemNumbers(problems); + } + + let totalProblemNumbers: number = 0; + for (const problems of problemsInfoAfterAutofixed.values()) { + totalProblemNumbers += problems.length; + } + + const canBeAutoFixedproblemNumbers = getMapValueSum(ruleToAutoFixedNumbersMap); + + const problemNumbers: ProblemNumbersInfo = { + totalProblemNumbers: totalProblemNumbers, + arkOnePointOneProblemNumbers: arkOnePointOneProblemNumbers, + arkOnePointTWOProblemNumbers: totalProblemNumbers - arkOnePointOneProblemNumbers, + canBeAutoFixedproblemNumbers: canBeAutoFixedproblemNumbers, + needToManualFixproblemNumbers: totalProblemNumbers - canBeAutoFixedproblemNumbers + }; + + const statisticsReportData = getProblemStatisticsInfo( + problemNumbers, + ruleToNumbersMap, + ruleToAutoFixedNumbersMap, + timeRecorder + ); + const statisticsReportName: string = 'migration-results-statistics.json'; + generateReportFileSync(statisticsReportName, statisticsReportData, outputFilePath); +} + +export async function getWorkLoadInfo(projectPathList: string[]): Promise { + const workloadInfo = new WorkLoadInfo(); + const projectResults = await Promise.all( + projectPathList.map(async(projectPath) => { + const normalizedPath = path.normalize(projectPath); + const [countFilesResults, countNapiFileResults] = await Promise.all([ + countFiles(normalizedPath), + countNapiFiles(normalizedPath) + ]); + + const getLines = (lang: keyof typeof countFilesResults): number => { + return countFilesResults[lang]?.lineCount || 0; + }; + + return { + normalizedPath, + arkTSCodeLines: getLines('ArkTS') + getLines('ArkTS Test'), + cAndCPPCodeLines: getLines('C/C++'), + napiCodeLines: countNapiFileResults.napiLines, + jsCodeLines: getLines('JavaScript'), + tsCodeLines: getLines('TypeScript'), + jsonCodeLines: getLines('JSON'), + xmlCodeLines: getLines('XML') + }; + }) + ); + + projectResults.forEach((result) => { + workloadInfo.addFloderResult(result); + }); + return workloadInfo; +} diff --git a/ets2panda/linter/src/lib/statistics/scan/ProblemStatisticsInfo.ts b/ets2panda/linter/src/lib/statistics/scan/ProblemStatisticsInfo.ts new file mode 100644 index 0000000000000000000000000000000000000000..8bbef29b1616af2b7c19476f7a227c429bf6145d --- /dev/null +++ b/ets2panda/linter/src/lib/statistics/scan/ProblemStatisticsInfo.ts @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { RuleDetailedErrorInfo } from './RuleDetailedErrorInfo'; +import type { WorkLoadInfo } from './WorkLoadInfo'; + +export interface ProblemStatisticsInfo { + problems: { [k: string]: number }; + usedTime: { [k: string]: string }; + eachRuleProblemsDetail: RuleDetailedErrorInfo[]; + WorkLoadInfo?: WorkLoadInfo; +} diff --git a/ets2panda/linter/src/lib/statistics/scan/RuleDetailedErrorInfo.ts b/ets2panda/linter/src/lib/statistics/scan/RuleDetailedErrorInfo.ts new file mode 100644 index 0000000000000000000000000000000000000000..b4c56ee615babc9f984bdb45ab357179e661b1cd --- /dev/null +++ b/ets2panda/linter/src/lib/statistics/scan/RuleDetailedErrorInfo.ts @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface RuleDetailedErrorInfo { + rule: string; + problemNumbers: number; + canBeAutoFixedMumbers: number; +} diff --git a/ets2panda/linter/src/lib/statistics/scan/ScanTaskRelatedInfo.ts b/ets2panda/linter/src/lib/statistics/scan/ScanTaskRelatedInfo.ts new file mode 100644 index 0000000000000000000000000000000000000000..6cc3f6dafdb3dcb3baa4d063562040b653996486 --- /dev/null +++ b/ets2panda/linter/src/lib/statistics/scan/ScanTaskRelatedInfo.ts @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { CommandLineOptions } from '../../CommandLineOptions'; +import type { LinterConfig } from '../../LinterConfig'; +import type { ProblemInfo } from '../../ProblemInfo'; +import type { StatisticsReportInPutInfo } from './StatisticsReportInPutInfo'; +import type { TimeRecorder } from './TimeRecorder'; + +export interface ScanTaskRelatedInfo { + cmdOptions: CommandLineOptions; + timeRecorder: TimeRecorder; + statisticsReportInPutInfo: StatisticsReportInPutInfo; + mergedProblems: Map; + homeCheckResult: Map; + totalProblemNumbers: number; + statisticsReportName: number; + compileOptions: LinterConfig; +} diff --git a/ets2panda/linter/src/lib/statistics/scan/StatisticsReportInPutInfo.ts b/ets2panda/linter/src/lib/statistics/scan/StatisticsReportInPutInfo.ts new file mode 100644 index 0000000000000000000000000000000000000000..377f0843ff0c02c3b21c8ad500447b183c85d04f --- /dev/null +++ b/ets2panda/linter/src/lib/statistics/scan/StatisticsReportInPutInfo.ts @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { CommandLineOptions } from '../../CommandLineOptions'; +import type { TimeRecorder } from './TimeRecorder'; + +export class StatisticsReportInPutInfo { + totalProblemNumbers: number = 0; + arkOnePointOneProblemNumbers: number = 0; + ruleToNumbersMap: Map = {} as Map; + ruleToAutoFixedNumbersMap: Map = {} as Map; + cmdOptions: CommandLineOptions = {} as CommandLineOptions; + timeRecorder: TimeRecorder = {} as TimeRecorder; + statisticsReportName: string = ''; +} diff --git a/ets2panda/linter/src/lib/statistics/scan/TimeRecorder.ts b/ets2panda/linter/src/lib/statistics/scan/TimeRecorder.ts new file mode 100644 index 0000000000000000000000000000000000000000..5f21944a8125872db5d872bbc43402fc91b7c47d --- /dev/null +++ b/ets2panda/linter/src/lib/statistics/scan/TimeRecorder.ts @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class TimeRecorder { + private lintStart = BigInt(0); + private lintEnd = BigInt(0); + private migrationStart = BigInt(0); + private migrationEnd = BigInt(0); + private isHomeCheckCount: boolean = false; + + setHomeCheckCountStatus(isHomeCheckCount: boolean): void { + this.isHomeCheckCount = isHomeCheckCount; + } + + getHomeCheckCountStatus(): boolean { + return this.isHomeCheckCount; + } + + startScan(): void { + this.lintStart = process.hrtime.bigint(); + } + + endScan(): void { + this.lintEnd = process.hrtime.bigint(); + } + + startMigration(): void { + this.migrationStart = process.hrtime.bigint(); + } + + endMigration(): void { + this.migrationEnd = process.hrtime.bigint(); + } + + getScanTime(): string { + return `${(Number(this.lintEnd - this.lintStart) / 1000000000).toFixed(2)} s`; + } + + getMigrationTime(): string { + return `${(Number(this.migrationEnd - this.migrationStart) / 1000000000).toFixed(2)} s`; + } +} diff --git a/ets2panda/linter/src/lib/statistics/scan/WorkLoadInfo.ts b/ets2panda/linter/src/lib/statistics/scan/WorkLoadInfo.ts new file mode 100644 index 0000000000000000000000000000000000000000..59135b1db29f1c9f48cc3485dd3dc05c263fb02c --- /dev/null +++ b/ets2panda/linter/src/lib/statistics/scan/WorkLoadInfo.ts @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + AVERAGE_LINE_FOR_REPAIRE_RULE_COEFFICIENT, + NPAI_REPAIRE_WORKLOADA_COEFFICIEN, + TEST_DEBUG_WORKLOAD_COEFFICIENT +} from '../../utils/consts/WorkloadRelatedConst'; +import type { FloderScanResultInfo } from './FloderScanResultInfo'; +import type { ProblemNumbersInfo } from './ProblemNumbersInfo'; + +export class WorkLoadInfo { + scanFilePathList: string[] = []; + totalArkTSCodeLines = 0; + totalCAndCPPCodeLines = 0; + totalNapiCodeLines = 0; + totalJsCodeLines = 0; + totalTsCodeLines = 0; + totalJsonCodeLines = 0; + totalXmlCodeLines = 0; + + addFloderResult(result: FloderScanResultInfo): void { + this.scanFilePathList.push(result.normalizedPath); + this.totalArkTSCodeLines += result.arkTSCodeLines; + this.totalCAndCPPCodeLines += result.cAndCPPCodeLines; + this.totalNapiCodeLines += result.napiCodeLines; + this.totalJsCodeLines += result.jsCodeLines; + this.totalTsCodeLines += result.tsCodeLines; + this.totalJsonCodeLines += result.jsonCodeLines; + this.totalXmlCodeLines += result.xmlCodeLines; + } + + calculateFixRate(problemNumbers: ProblemNumbersInfo): string { + const totalLines = this.totalArkTSCodeLines + this.totalCAndCPPCodeLines; + if (totalLines <= 0) { + return '0.00%'; + } + + const problemCount = problemNumbers.needToManualFixproblemNumbers; + const ratio = + (problemCount * AVERAGE_LINE_FOR_REPAIRE_RULE_COEFFICIENT * TEST_DEBUG_WORKLOAD_COEFFICIENT + + this.totalNapiCodeLines * NPAI_REPAIRE_WORKLOADA_COEFFICIEN) / + totalLines; + + return `${(ratio * 100).toFixed(2)}%`; + } +} diff --git a/ets2panda/linter/src/lib/utils/consts/MapKeyConst.ts b/ets2panda/linter/src/lib/utils/consts/MapKeyConst.ts new file mode 100644 index 0000000000000000000000000000000000000000..e5689d3f2a3f30ba320d6d5b5b6351c6bae229f1 --- /dev/null +++ b/ets2panda/linter/src/lib/utils/consts/MapKeyConst.ts @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const TOTAL_PROBLEMS = 'totalProblems'; + +export const ONE_POINT_ONE_PROBLEMS = 'arts1.1_Problems'; + +export const ONE_POINT_TWO_PROBLEMS = 'arts1.2_Problems'; + +export const CAN_BE_AUTO_FIXED_PROBLEMS_NUMBERS = 'canBeAutoFixedproblemNumbers'; + +export const NEED_TO_NAMUAL_FIX_PROBLEM_NUMBERS = 'needToManualFixproblemNumbers'; + +export const SCAN_TIME = 'scanTime'; + +export const MIGRATION_TIME = 'migrationTime'; diff --git a/ets2panda/linter/src/lib/utils/consts/WorkloadRelatedConst.ts b/ets2panda/linter/src/lib/utils/consts/WorkloadRelatedConst.ts new file mode 100644 index 0000000000000000000000000000000000000000..c0c5efc0cc019faef4845c53ba7dde729c9e4f7b --- /dev/null +++ b/ets2panda/linter/src/lib/utils/consts/WorkloadRelatedConst.ts @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const AVERAGE_LINE_FOR_REPAIRE_RULE_COEFFICIENT = 3; + +export const TEST_DEBUG_WORKLOAD_COEFFICIENT = 1.2; + +export const NPAI_REPAIRE_WORKLOADA_COEFFICIEN = 0.2; \ No newline at end of file diff --git a/ets2panda/linter/src/testRunner/LintTest.ts b/ets2panda/linter/src/testRunner/LintTest.ts index 02db53dd4993067c10a6897c7fc08d5b0563dc62..8661ed7c746ddf2b97345282a0142f07d8e7b1db 100644 --- a/ets2panda/linter/src/testRunner/LintTest.ts +++ b/ets2panda/linter/src/testRunner/LintTest.ts @@ -15,20 +15,21 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; +import type { Autofix } from '../lib/autofixes/Autofixer'; import type { CommandLineOptions } from '../lib/CommandLineOptions'; import type { LinterConfig } from '../lib/LinterConfig'; import { lint } from '../lib/LinterRunner'; +import type { LintRunResult } from '../lib/LintRunResult'; import { Logger } from '../lib/Logger'; +import { TimeRecorder } from '../lib/statistics/scan/TimeRecorder'; import { compileLintOptions } from '../lib/ts-compiler/Compiler'; +import { DIFF_EXT, RESULTS_DIR, TAB } from './Consts'; +import type { CreateLintTestOptions } from './TestFactory'; import type { TestModeProperties } from './TestMode'; import { TestMode } from './TestMode'; -import { transformProblemInfos } from './TestUtil'; import type { TestProblemInfo, TestResult } from './TestResult'; import { validateTestResult } from './TestResult'; -import type { LintRunResult } from '../lib/LintRunResult'; -import { DIFF_EXT, RESULTS_DIR, TAB } from './Consts'; -import type { Autofix } from '../lib/autofixes/Autofixer'; -import type { CreateLintTestOptions } from './TestFactory'; +import { transformProblemInfos } from './TestUtil'; export class LintTest { readonly testDir: string; @@ -47,7 +48,8 @@ export class LintTest { Logger.info(`Running test ${this.testFile} (${TestMode[this.testModeProps.mode]} mode)`); const linterConfig = this.compile(); - const linterResult = lint(linterConfig); + const timeRecorder = new TimeRecorder(); + const linterResult = lint(linterConfig, timeRecorder); return this.validate(linterResult); }