From 6f8fa6ef59992da119909681a10dc9bff2a96339 Mon Sep 17 00:00:00 2001 From: ohhusenlin Date: Fri, 20 Jun 2025 17:10:12 +0800 Subject: [PATCH] Add detailed statistics report Issue: https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/ICGQPK Signed-off-by: ohhusenlin --- ets2panda/linter/src/cli/CommandLineParser.ts | 2 + ets2panda/linter/src/cli/LinterCLI.ts | 133 +++++++--- ets2panda/linter/src/lib/LintRunResult.ts | 2 + ets2panda/linter/src/lib/LinterOptions.ts | 1 + ets2panda/linter/src/lib/LinterRunner.ts | 14 +- .../src/lib/utils/consts/MapKeyConst.ts | 40 +++ .../src/lib/utils/functions/CountFile.ts | 216 +++++++++++++++ .../src/lib/utils/functions/CountNapiFile.ts | 121 +++++++++ .../ProblemStatisticsCommonFunction.ts | 245 ++++++++++++++++++ .../utils/interface/ManualFixWorkLoadInfo.ts | 26 ++ .../utils/interface/NapiFileStatisticInfo.ts | 22 ++ .../src/lib/utils/interface/ProblemNumbers.ts | 22 ++ .../utils/interface/ProblemStatisticsInfo.ts | 24 ++ .../src/lib/utils/interface/RuleCount.ts | 19 ++ .../utils/interface/RuleDetailedErrorInfo.ts | 20 ++ .../utils/interface/ScanTaskRelatedInfo.ts | 31 +++ .../interface/StatisticsReportInPutInfo.ts | 27 ++ .../src/lib/utils/interface/TimeRecorder.ts | 54 ++++ ets2panda/linter/src/testRunner/LintTest.ts | 4 +- 19 files changed, 983 insertions(+), 40 deletions(-) create mode 100644 ets2panda/linter/src/lib/utils/consts/MapKeyConst.ts create mode 100644 ets2panda/linter/src/lib/utils/functions/CountFile.ts create mode 100644 ets2panda/linter/src/lib/utils/functions/CountNapiFile.ts create mode 100644 ets2panda/linter/src/lib/utils/functions/ProblemStatisticsCommonFunction.ts create mode 100644 ets2panda/linter/src/lib/utils/interface/ManualFixWorkLoadInfo.ts create mode 100644 ets2panda/linter/src/lib/utils/interface/NapiFileStatisticInfo.ts create mode 100644 ets2panda/linter/src/lib/utils/interface/ProblemNumbers.ts create mode 100644 ets2panda/linter/src/lib/utils/interface/ProblemStatisticsInfo.ts create mode 100644 ets2panda/linter/src/lib/utils/interface/RuleCount.ts create mode 100644 ets2panda/linter/src/lib/utils/interface/RuleDetailedErrorInfo.ts create mode 100644 ets2panda/linter/src/lib/utils/interface/ScanTaskRelatedInfo.ts create mode 100644 ets2panda/linter/src/lib/utils/interface/StatisticsReportInPutInfo.ts create mode 100644 ets2panda/linter/src/lib/utils/interface/TimeRecorder.ts diff --git a/ets2panda/linter/src/cli/CommandLineParser.ts b/ets2panda/linter/src/cli/CommandLineParser.ts index 9849eedf17..a7be723743 100644 --- a/ets2panda/linter/src/cli/CommandLineParser.ts +++ b/ets2panda/linter/src/cli/CommandLineParser.ts @@ -177,6 +177,8 @@ function formCommandLineOptions(parsedCmd: ParsedCommand): CommandLineOptions { } if (options.projectFolder) { doProjectFolderArg(options.projectFolder, opts); + const projectLength = options.projectFolder.length; + opts.linterOptions.projectFolder = options.projectFolder[projectLength - 1]; } if (options.project) { doProjectArg(options.project, opts); diff --git a/ets2panda/linter/src/cli/LinterCLI.ts b/ets2panda/linter/src/cli/LinterCLI.ts index 5af05d0e31..0df81ae97c 100644 --- a/ets2panda/linter/src/cli/LinterCLI.ts +++ b/ets2panda/linter/src/cli/LinterCLI.ts @@ -27,6 +27,10 @@ import { logStatistics } from '../lib/statistics/StatisticsLogger'; import { arkts2Rules, onlyArkts2SyntaxRules } from '../lib/utils/consts/ArkTS2Rules'; import { MigrationTool } from 'homecheck'; import { getHomeCheckConfigInfo, transferIssues2ProblemInfo } from '../lib/HomeCheck'; +import * as statistic from '../lib/utils/functions/ProblemStatisticsCommonFunction'; +import { TimeRecorder } from '../lib/utils/interface/TimeRecorder'; +import { StatisticsReportInPutInfo } from '../lib/utils/interface/StatisticsReportInPutInfo'; +import type { ScanTaskRelatedInfo } from '../lib/utils/interface/ScanTaskRelatedInfo'; 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(); + scanTaskRelatedInfo.cmdOptions = cmdOptions; + scanTaskRelatedInfo.timeRecorder = timeRecorder; + scanTaskRelatedInfo.compileOptions = compileOptions; + await executeScanTask(scanTaskRelatedInfo); - 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); - } - } + const statisticsReportInPutInfo = scanTaskRelatedInfo.statisticsReportInPutInfo; + statisticsReportInPutInfo.statisticsReportName = 'scan-problems-statistics.json'; + statisticsReportInPutInfo.totalProblemNumbers = getTotalProblemNumbers(scanTaskRelatedInfo.mergedProblems); + statisticsReportInPutInfo.cmdOptions = cmdOptions; + statisticsReportInPutInfo.timeRecorder = timeRecorder; - if (!cmdOptions.skipLinter) { - const result = lint(compileOptions, getEtsLoaderPath(compileOptions), homeCheckResult); - for (const [filePath, problems] of result.problemsInfos) { - mergeLintProblems(filePath, problems, mergedProblems, cmdOptions); - } + if (!cmdOptions.linterOptions.migratorMode) { + 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[], @@ -133,18 +202,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); - } -} async function processSyncOut(message: string): Promise { await new Promise((resolve) => { @@ -204,7 +261,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 3397a3941a..21c8d77a1f 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 '../lib/utils/interface/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 72613a4cd1..cb32ef461b 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; + projectFolder?: string; checkTsAndJs?: boolean; inputFiles?: string[]; } diff --git a/ets2panda/linter/src/lib/LinterRunner.ts b/ets2panda/linter/src/lib/LinterRunner.ts index 453731f7bc..2e9b11194d 100644 --- a/ets2panda/linter/src/lib/LinterRunner.ts +++ b/ets2panda/linter/src/lib/LinterRunner.ts @@ -41,6 +41,8 @@ import { compileLintOptions } from './ts-compiler/Compiler'; import * as qEd from './autofixes/QuasiEditor'; import { ProjectStatistics } from './statistics/ProjectStatistics'; import type { BaseTypeScriptLinter } from './BaseTypeScriptLinter'; +import type { TimeRecorder } from '../lib/utils/interface/TimeRecorder'; +import { generateMigrationStatisicsReport } from './utils/functions/ProblemStatisticsCommonFunction'; function prepareInputFilesList(cmdOptions: CommandLineOptions): string[] { let inputFiles = cmdOptions.inputFiles.map((x) => { @@ -76,6 +78,7 @@ function prepareInputFilesList(cmdOptions: CommandLineOptions): string[] { export function lint( config: LinterConfig, + timeRecorder: TimeRecorder, etsLoaderPath?: string, hcResults?: Map ): LintRunResult { @@ -83,7 +86,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 { @@ -150,8 +156,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(); @@ -182,6 +190,9 @@ function migrate( fs.writeFileSync(writeFileName, newText); }); + timeRecorder.endMigration(); + generateMigrationStatisicsReport(lintResult, timeRecorder, cmdOptions.outputFilePath); + if (cmdOptions.linterOptions.ideInteractive) { lintResult.problemsInfos = problemsInfosBeforeMigrate; } @@ -189,6 +200,7 @@ function migrate( return lintResult; } + function hasUseStaticDirective(srcFile: ts.SourceFile): boolean { if (!srcFile?.statements.length) { return false; 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 0000000000..448a3755d4 --- /dev/null +++ b/ets2panda/linter/src/lib/utils/consts/MapKeyConst.ts @@ -0,0 +1,40 @@ +/* + * 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 RULE_KEY = 'ruleid'; + +export const Auto_FIXED_PROBLEM_NUMBERS = 'autoFixedProblem'; + +export const PROBLEM_NUMBERS = 'problemNumbers'; + +export const TOTAL_PROBLEMS = 'totalProblems'; + +export const EACH_RULE_PROBLEMS_DETAIL = 'eachRuleProblemsDetail'; + +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 TO_BE_FIXED_BY_MANUAL_PROBLEMS = 'toBefixedByManualProblems'; + +export const USED_TIME = 'usedTime'; + +export const SCAN_TIME = 'scanTime'; + +export const MIGRATION_TIME = 'migrationTime'; diff --git a/ets2panda/linter/src/lib/utils/functions/CountFile.ts b/ets2panda/linter/src/lib/utils/functions/CountFile.ts new file mode 100644 index 0000000000..04f0166f35 --- /dev/null +++ b/ets2panda/linter/src/lib/utils/functions/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/utils/functions/CountNapiFile.ts b/ets2panda/linter/src/lib/utils/functions/CountNapiFile.ts new file mode 100644 index 0000000000..87177eb60d --- /dev/null +++ b/ets2panda/linter/src/lib/utils/functions/CountNapiFile.ts @@ -0,0 +1,121 @@ +/* + * 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 '../interface/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.includes('napi') || line.includes('NAPI') || line.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 stats: NapiFileStatisticInfo = { ...DEFAULT_STATISTICS }; + + async function walkDir(currentPath: string): Promise { + const entries = await fs.promises.readdir(currentPath, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(currentPath, entry.name); + + if (entry.isDirectory()) { + await walkDir(fullPath); + } else if ( + EXTENSIONS.some((ext) => { + return entry.name.endsWith(ext); + }) + ) { + stats.totalFiles++; + const lines = await countLines(fullPath); + stats.totalLines += lines; + + const napiCount = await countNapiLines(fullPath); + if (napiCount > 0) { + stats.napiFiles++; + stats.napiLines += napiCount; + stats.napiFileLines += lines; + } + } + } + } + + try { + await walkDir(directory); + } catch (e) { + console.error(`Error walking directory ${directory}: ${e}`); + } + + return stats; +} + +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/utils/functions/ProblemStatisticsCommonFunction.ts b/ets2panda/linter/src/lib/utils/functions/ProblemStatisticsCommonFunction.ts new file mode 100644 index 0000000000..526bbd2371 --- /dev/null +++ b/ets2panda/linter/src/lib/utils/functions/ProblemStatisticsCommonFunction.ts @@ -0,0 +1,245 @@ +/* + * 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 * as mk from '../consts/MapKeyConst'; +import type { RuleDetailedErrorInfo } from '../interface/RuleDetailedErrorInfo'; +import type { ProblemInfo } from '../../ProblemInfo'; +import type { ProblemStatisticsInfo } from '../interface/ProblemStatisticsInfo'; +import type { TimeRecorder } from '../interface/TimeRecorder'; +import type { ProblemNumbers } from '../interface/ProblemNumbers'; +import { countFiles } from '../functions/CountFile'; +import { countNapiFiles } from '../functions/CountNapiFile'; +import type { ManualFixWorkLoadInfo } from '../interface/ManualFixWorkLoadInfo'; +import type { StatisticsReportInPutInfo } from '../../../lib/utils/interface/StatisticsReportInPutInfo'; +import { LintRunResult } from '../../LintRunResult'; + +export function getProblemStatisticsInfo( + problemNumbers: ProblemNumbers, + ruleToNumbersMap: Map, + ruleToAutoFixedNumbersMap: Map, + timeRecorder: TimeRecorder, + manualFixWorkLoadInfo?: ManualFixWorkLoadInfo +): 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), + manualFixWorkLoadInfo: manualFixWorkLoadInfo, + 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: ProblemNumbers = { + totalProblemNumbers: statisticsReportInPutInfo.totalProblemNumbers, + arkOnePointOneProblemNumbers: statisticsReportInPutInfo.arkOnePointOneProblemNumbers, + arkOnePointTWOProblemNumbers: + statisticsReportInPutInfo.totalProblemNumbers - statisticsReportInPutInfo.arkOnePointOneProblemNumbers, + canBeAutoFixedproblemNumbers: canBeAutoFixedproblemNumbers, + needToManualFixproblemNumbers: statisticsReportInPutInfo.totalProblemNumbers - canBeAutoFixedproblemNumbers + }; + + const manualFixWorkLoadInfo = await getManualFixWorkLoadInfo( + problemNumbers, + statisticsReportInPutInfo.cmdOptions.linterOptions.projectFolder! + ); + const statisticsReportData = getProblemStatisticsInfo( + problemNumbers, + statisticsReportInPutInfo.ruleToNumbersMap, + statisticsReportInPutInfo.ruleToAutoFixedNumbersMap, + statisticsReportInPutInfo.timeRecorder, + manualFixWorkLoadInfo + ); + 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: ProblemNumbers = { + 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 getManualFixWorkLoadInfo( + problemNumbers: ProblemNumbers, + projectPath: string +): Promise { + 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; + }; + + // 计算关键行数指标 + const arkTSCodeLines = getLines('ArkTS') + getLines('ArkTS Test'); + const cAndCPPCodeLines = getLines('C/C++'); + const totalLines = arkTSCodeLines + cAndCPPCodeLines; + + // 计算手动修复率 + const calculateFixRate = (): string => { + if (totalLines <= 0) { + return '0.00%'; + } + const problemCount = problemNumbers.needToManualFixproblemNumbers; + const ratio = (problemCount * 3.6 + countNapiFileResults.napiLines * 0.2) / totalLines; + return `${(ratio * 100).toFixed(2)}%`; + }; + + return { + manualFixRate: calculateFixRate(), + arkTSCodeLines: arkTSCodeLines, + cAndCPPCodeLines: cAndCPPCodeLines, + napiCodeLines: countNapiFileResults.napiLines, + jsCodeLines: getLines('JavaScript'), + tsCodeLines: getLines('TypeScript'), + jsonCodeLines: getLines('JSON'), + xmlCodeLines: getLines('XML'), + scanFilePath: normalizedPath + }; +} diff --git a/ets2panda/linter/src/lib/utils/interface/ManualFixWorkLoadInfo.ts b/ets2panda/linter/src/lib/utils/interface/ManualFixWorkLoadInfo.ts new file mode 100644 index 0000000000..75f82b3e9b --- /dev/null +++ b/ets2panda/linter/src/lib/utils/interface/ManualFixWorkLoadInfo.ts @@ -0,0 +1,26 @@ +/* + * 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 ManualFixWorkLoadInfo { + arkTSCodeLines: number; + cAndCPPCodeLines: number; + napiCodeLines: number; + jsCodeLines: number; + tsCodeLines: number; + jsonCodeLines: number; + xmlCodeLines: number; + manualFixRate: string; + scanFilePath: string; +} diff --git a/ets2panda/linter/src/lib/utils/interface/NapiFileStatisticInfo.ts b/ets2panda/linter/src/lib/utils/interface/NapiFileStatisticInfo.ts new file mode 100644 index 0000000000..20b3243802 --- /dev/null +++ b/ets2panda/linter/src/lib/utils/interface/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/utils/interface/ProblemNumbers.ts b/ets2panda/linter/src/lib/utils/interface/ProblemNumbers.ts new file mode 100644 index 0000000000..7e798f88d9 --- /dev/null +++ b/ets2panda/linter/src/lib/utils/interface/ProblemNumbers.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 ProblemNumbers { + totalProblemNumbers: number; + arkOnePointOneProblemNumbers: number; + arkOnePointTWOProblemNumbers: number; + canBeAutoFixedproblemNumbers: number; + needToManualFixproblemNumbers: number; +} diff --git a/ets2panda/linter/src/lib/utils/interface/ProblemStatisticsInfo.ts b/ets2panda/linter/src/lib/utils/interface/ProblemStatisticsInfo.ts new file mode 100644 index 0000000000..dd3acf491a --- /dev/null +++ b/ets2panda/linter/src/lib/utils/interface/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 { ManualFixWorkLoadInfo } from './ManualFixWorkLoadInfo'; + +export interface ProblemStatisticsInfo { + problems: { [k: string]: number }; + usedTime: { [k: string]: string }; + eachRuleProblemsDetail: RuleDetailedErrorInfo[]; + manualFixWorkLoadInfo?: ManualFixWorkLoadInfo; +} diff --git a/ets2panda/linter/src/lib/utils/interface/RuleCount.ts b/ets2panda/linter/src/lib/utils/interface/RuleCount.ts new file mode 100644 index 0000000000..6998b12c48 --- /dev/null +++ b/ets2panda/linter/src/lib/utils/interface/RuleCount.ts @@ -0,0 +1,19 @@ +/* + * 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 RuleCount { + ruleId: Set; + count: number; +} diff --git a/ets2panda/linter/src/lib/utils/interface/RuleDetailedErrorInfo.ts b/ets2panda/linter/src/lib/utils/interface/RuleDetailedErrorInfo.ts new file mode 100644 index 0000000000..b4c56ee615 --- /dev/null +++ b/ets2panda/linter/src/lib/utils/interface/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/utils/interface/ScanTaskRelatedInfo.ts b/ets2panda/linter/src/lib/utils/interface/ScanTaskRelatedInfo.ts new file mode 100644 index 0000000000..6cc3f6dafd --- /dev/null +++ b/ets2panda/linter/src/lib/utils/interface/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/utils/interface/StatisticsReportInPutInfo.ts b/ets2panda/linter/src/lib/utils/interface/StatisticsReportInPutInfo.ts new file mode 100644 index 0000000000..e9e3afba42 --- /dev/null +++ b/ets2panda/linter/src/lib/utils/interface/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 '../../../lib/CommandLineOptions'; +import type { TimeRecorder } from '../../../lib/utils/interface/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/utils/interface/TimeRecorder.ts b/ets2panda/linter/src/lib/utils/interface/TimeRecorder.ts new file mode 100644 index 0000000000..12b034d852 --- /dev/null +++ b/ets2panda/linter/src/lib/utils/interface/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 = 0; + private lintEnd = 0; + private migrationStart = 0; + private migrationEnd = 0; + private isHomeCheckCount: boolean = false; + + setHomeCheckCountStatus(isHomeCheckCount: boolean): void { + this.isHomeCheckCount = isHomeCheckCount; + } + + getHomeCheckCountStatus(): boolean { + return this.isHomeCheckCount; + } + + startScan(): void { + this.lintStart = performance.now(); + } + + endScan(): void { + this.lintEnd = performance.now(); + } + + startMigration(): void { + this.migrationStart = performance.now(); + } + + endMigration(): void { + this.migrationEnd = performance.now(); + } + + getScanTime(): string { + return `${((this.lintEnd - this.lintStart) / 1000).toFixed(2)} s`; + } + + getMigrationTime(): string { + return `${((this.migrationEnd - this.migrationStart) / 1000).toFixed(2)} s`; + } +} diff --git a/ets2panda/linter/src/testRunner/LintTest.ts b/ets2panda/linter/src/testRunner/LintTest.ts index 02db53dd49..78fdff0400 100644 --- a/ets2panda/linter/src/testRunner/LintTest.ts +++ b/ets2panda/linter/src/testRunner/LintTest.ts @@ -29,6 +29,7 @@ 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 { TimeRecorder } from '../lib/utils/interface/TimeRecorder'; 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); } -- Gitee