From 3d7f57855a6cdd9c08449584fb72e97b19b2404d Mon Sep 17 00:00:00 2001 From: ohhusenlin Date: Sat, 21 Jun 2025 20:39:53 +0800 Subject: [PATCH 1/2] ArkTS rule can be configed by user Issue: https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/ICGV8G Signed-off-by: ohhusenlin --- ets2panda/linter/src/cli/CommandLineParser.ts | 28 ++++++-- .../linter/src/lib/BaseTypeScriptLinter.ts | 52 ++++++++------ .../linter/src/lib/CommandLineOptions.ts | 1 + ets2panda/linter/src/lib/LinterOptions.ts | 1 + .../src/lib/utils/consts/ArkTS2Rules.ts | 14 +++- .../utils/functions/ConfiguredRulesProcess.ts | 68 +++++++++++++++++++ .../src/lib/utils/functions/CookBookUtils.ts | 35 ++++++++++ 7 files changed, 173 insertions(+), 26 deletions(-) create mode 100644 ets2panda/linter/src/lib/utils/functions/ConfiguredRulesProcess.ts create mode 100644 ets2panda/linter/src/lib/utils/functions/CookBookUtils.ts diff --git a/ets2panda/linter/src/cli/CommandLineParser.ts b/ets2panda/linter/src/cli/CommandLineParser.ts index 42e2cf404b..ac4fd84acc 100644 --- a/ets2panda/linter/src/cli/CommandLineParser.ts +++ b/ets2panda/linter/src/cli/CommandLineParser.ts @@ -13,15 +13,18 @@ * 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 { cookBookTag } from '../lib/CookBookMsg'; +import { Logger } from '../lib/Logger'; +import { ARKTS_IGNORE_DIRS_OH_MODULES } from '../lib/utils/consts/ArktsIgnorePaths'; +import { getConfiguredRuleTags, getRulesFromConfig } from '../lib/utils/functions/ConfiguredRulesProcess'; +import { extractRuleTags } from '../lib/utils/functions/CookBookUtils'; +import { logTscDiagnostic } from '../lib/utils/functions/LogTscDiagnostic'; const TS_EXT = '.ts'; const TSX_EXT = '.tsx'; @@ -192,6 +195,7 @@ function formCommandLineOptions(parsedCmd: ParsedCommand): CommandLineOptions { if (options.useRtLogic !== undefined) { opts.linterOptions.useRtLogic = options.useRtLogic; } + processRuleConfig(opts, options); formIdeInteractive(opts, options); formSdkOptions(opts, options); formMigrateOptions(opts, options); @@ -199,6 +203,19 @@ function formCommandLineOptions(parsedCmd: ParsedCommand): CommandLineOptions { return opts; } +function processRuleConfig(commandLineOptions: CommandLineOptions, options: OptionValues): void { + if (options.ruleConfig !== undefined) { + const stats = fs.statSync(path.normalize(options.ruleConfig)); + if (!stats.isFile()) { + console.error(`The file at ${options.ruleConfigPath} path does not exist!`); + } else { + const configuredRulesMap = getRulesFromConfig(options.ruleConfig); + const arkTSRulesMap = extractRuleTags(cookBookTag); + commandLineOptions.linterOptions.ruleConfigTags = getConfiguredRuleTags(arkTSRulesMap, configuredRulesMap); + } + } +} + function createCommand(): Command { const program = new Command(); program. @@ -235,6 +252,7 @@ function createCommand(): Command { option('-o, --output-file-path ', 'path to store all log and result files'). option('--verbose', 'set log level to see debug messages'). option('--enable-interop', 'scan whole project to report 1.1 import 1.2'). + option('--rule-config ', 'Path to the rule configuration file'). addOption(new Option('--warnings-as-errors', 'treat warnings as errors').hideHelp(true)). addOption(new Option('--no-check-ts-as-source', 'check TS files as third-party libary').hideHelp(true)). addOption(new Option('--no-use-rt-logic', 'run linter with SDK logic').hideHelp(true)). diff --git a/ets2panda/linter/src/lib/BaseTypeScriptLinter.ts b/ets2panda/linter/src/lib/BaseTypeScriptLinter.ts index b756543ab9..cb711335e4 100644 --- a/ets2panda/linter/src/lib/BaseTypeScriptLinter.ts +++ b/ets2panda/linter/src/lib/BaseTypeScriptLinter.ts @@ -76,13 +76,31 @@ export abstract class BaseTypeScriptLinter { autofix?: Autofix[], errorMsg?: string ): void { + const badNodeInfo = this.getbadNodeInfo(node, faultId, autofix, errorMsg); + + if (this.shouldSkipRule(badNodeInfo)) { + return; + } + + this.problemsInfos.push(badNodeInfo); + this.updateFileStats(faultId, badNodeInfo.line); + // problems with autofixes might be collected separately + if (this.options.reportAutofixCb && badNodeInfo.autofix) { + this.options.reportAutofixCb(badNodeInfo); + } + } + + private getbadNodeInfo( + node: ts.Node | ts.CommentRange, + faultId: number, + autofix?: Autofix[], + errorMsg?: string + ): ProblemInfo { const [startOffset, endOffset] = TsUtils.getHighlightRange(node, faultId); const startPos = this.sourceFile.getLineAndCharacterOfPosition(startOffset); const endPos = this.sourceFile.getLineAndCharacterOfPosition(endOffset); - const faultDescr = faultDesc[faultId]; const faultType = TypeScriptLinterConfig.tsSyntaxKindNames[node.kind]; - const cookBookMsgNum = faultsAttrs[faultId] ? faultsAttrs[faultId].cookBookRef : 0; const cookBookTg = errorMsg ? errorMsg : cookBookTag[cookBookMsgNum]; const severity = faultsAttrs[faultId]?.severity ?? ProblemSeverity.ERROR; @@ -107,17 +125,7 @@ export abstract class BaseTypeScriptLinter { autofix: autofix, autofixTitle: isMsgNumValid && autofix !== undefined ? cookBookRefToFixTitle.get(cookBookMsgNum) : undefined }; - - if (this.options?.ideInteractive && this.isSkipedRecordProblems(badNodeInfo)) { - return; - } - - this.problemsInfos.push(badNodeInfo); - this.updateFileStats(faultId, badNodeInfo.line); - // problems with autofixes might be collected separately - if (this.options.reportAutofixCb && badNodeInfo.autofix) { - this.options.reportAutofixCb(badNodeInfo); - } + return badNodeInfo; } private static processAutofix( @@ -128,17 +136,21 @@ export abstract class BaseTypeScriptLinter { return autofix ? BaseTypeScriptLinter.addLineColumnInfoInAutofix(autofix, startPos, endPos) : autofix; } - private isSkipedRecordProblems(badNodeInfo: ProblemInfo): boolean { - if (this.options.onlySyntax) { - if (onlyArkts2SyntaxRules.has(badNodeInfo.ruleTag)) { + private shouldSkipRule(badNodeInfo: ProblemInfo): boolean { + const ruleConfigTags = this.options.ruleConfigTags; + if (ruleConfigTags && !ruleConfigTags.has(badNodeInfo.ruleTag)) { + return true; + } + if (this.options?.ideInteractive) { + if (this.options.onlySyntax) { + if (onlyArkts2SyntaxRules.has(badNodeInfo.ruleTag)) { return false; } - } else { - if (this.options.arkts2 - && arkts2Rules.includes(badNodeInfo.ruleTag)) { + } else if (this.options.arkts2 && arkts2Rules.includes(badNodeInfo.ruleTag)) { return false; } + return true; } - return true; + return false; } } diff --git a/ets2panda/linter/src/lib/CommandLineOptions.ts b/ets2panda/linter/src/lib/CommandLineOptions.ts index d793e62044..d1fffdfd15 100644 --- a/ets2panda/linter/src/lib/CommandLineOptions.ts +++ b/ets2panda/linter/src/lib/CommandLineOptions.ts @@ -33,4 +33,5 @@ export interface CommandLineOptions { outputFilePath?: string; verbose?: boolean; scanWholeProjectInHomecheck?: boolean; + ruleConfig?: string; } diff --git a/ets2panda/linter/src/lib/LinterOptions.ts b/ets2panda/linter/src/lib/LinterOptions.ts index 11c2183d5c..4c7e1d073e 100644 --- a/ets2panda/linter/src/lib/LinterOptions.ts +++ b/ets2panda/linter/src/lib/LinterOptions.ts @@ -47,4 +47,5 @@ export interface LinterOptions { checkTsAndJs?: boolean; inputFiles?: string[]; onlySyntax?: boolean; + ruleConfigTags?: Set; } diff --git a/ets2panda/linter/src/lib/utils/consts/ArkTS2Rules.ts b/ets2panda/linter/src/lib/utils/consts/ArkTS2Rules.ts index ea727f914a..e78c1006f1 100644 --- a/ets2panda/linter/src/lib/utils/consts/ArkTS2Rules.ts +++ b/ets2panda/linter/src/lib/utils/consts/ArkTS2Rules.ts @@ -192,5 +192,17 @@ export const onlyArkts2SyntaxRules: Map = new Map([ [255, 'arkts-no-extends-expression'], [300, 'arkts-no-ts-like-function-call'], [301, 'arkts-ohmurl-full-path'], - [304, 'arkts-no-duplicate-function-name'] + [304, 'arkts-no-duplicate-function-name'], + [319, 'arkts-method-inherit-rule'], + [325, 'arkts-default-args-behind-required-args'], + [329, 'arkts-unsupport-prop-name-from-value'], + [370, 'arkts-no-sparse-array'], + [371, 'arkts-no-enum-prop-as-type'], + [372, 'arkts-no-ts-like-smart-type'], + [373, 'arkts-array-type-immutable'], + [374, 'arkts-primitive-type-normalization'], + [375, 'arkts-no-ts-like-catch-type'], + [376, 'arkts-numeric-bigint-compare'], + [377, 'arkts-only-support-decimal-bigint-literal'], + [378, 'arkts-unsupport-operator'] ]); diff --git a/ets2panda/linter/src/lib/utils/functions/ConfiguredRulesProcess.ts b/ets2panda/linter/src/lib/utils/functions/ConfiguredRulesProcess.ts new file mode 100644 index 0000000000..99e6c5da6f --- /dev/null +++ b/ets2panda/linter/src/lib/utils/functions/ConfiguredRulesProcess.ts @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2023-2024 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'; + +export function getConfiguredRuleTags( + arkTSRulesMap: Map, + configuredRulesMap: Map +): Set { + const mergedRulesMap: string[] = Array.from(configuredRulesMap.values()).flat(); + const configuredRuleTags = new Set(); + const mergedRulesSet = new Set(mergedRulesMap); + arkTSRulesMap.forEach((key, value) => { + if (mergedRulesSet.has(key)) { + configuredRuleTags.add(value); + } + }); + return configuredRuleTags; +} + +export function getRulesFromConfig(configRulePath: string): Map { + try { + const normalizedPath = path.normalize(configRulePath); + const data = fs.readFileSync(normalizedPath, 'utf-8'); + const jsonData = JSON.parse(data); + const dataMap = new Map(); + for (const [key, value] of Object.entries(jsonData)) { + dataMap.set(key, value); + } + return convertToStringArrayMap(dataMap); + } catch (error) { + if (error instanceof SyntaxError) { + throw new Error(`JSON parsing failed: ${error.message}`); + } + return new Map(); + } +} + +function convertToStringArrayMap(inputMap: Map): Map { + const resultMap: Map = new Map(); + for (const [key, value] of inputMap) { + if (isStringArray(value)) { + resultMap.set(key, value); + } + } + return resultMap; +} + +function isStringArray(value: any): value is string[] { + return ( + Array.isArray(value) && + value.every((item) => { + return typeof item === 'string'; + }) + ); +} diff --git a/ets2panda/linter/src/lib/utils/functions/CookBookUtils.ts b/ets2panda/linter/src/lib/utils/functions/CookBookUtils.ts new file mode 100644 index 0000000000..0f2bec5ade --- /dev/null +++ b/ets2panda/linter/src/lib/utils/functions/CookBookUtils.ts @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2022-2024 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 function extractRuleTags(tags: string[]): Map { + const resultMap = new Map(); + + for (let i = 0; i < tags.length; i++) { + const tag = tags[i]; + + if (!tag?.trim()) { + continue; + } + + const regex = /\(([^)]+)\)/; + const match = tag.match(regex); + + if (match?.[1]?.trim()) { + resultMap.set(i, match[1]); + } + } + + return resultMap; +} -- Gitee From 44fe14cd1349642b4229f2caa15eb0ee09ff691c Mon Sep 17 00:00:00 2001 From: HuSenlin Date: Wed, 25 Jun 2025 21:55:34 +0800 Subject: [PATCH 2/2] Auto fix add configure rule Issue: https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/ICHU2N Signed-off-by: HuSenlin --- ets2panda/linter/src/cli/CommandLineParser.ts | 40 ++++++++++++++---- ets2panda/linter/src/lib/LinterOptions.ts | 1 + ets2panda/linter/src/lib/LinterRunner.ts | 41 ++++++++++++++----- 3 files changed, 63 insertions(+), 19 deletions(-) diff --git a/ets2panda/linter/src/cli/CommandLineParser.ts b/ets2panda/linter/src/cli/CommandLineParser.ts index ac4fd84acc..085eb8e42b 100644 --- a/ets2panda/linter/src/cli/CommandLineParser.ts +++ b/ets2panda/linter/src/cli/CommandLineParser.ts @@ -196,6 +196,7 @@ function formCommandLineOptions(parsedCmd: ParsedCommand): CommandLineOptions { opts.linterOptions.useRtLogic = options.useRtLogic; } processRuleConfig(opts, options); + processAutofixRuleConfig(opts, options); formIdeInteractive(opts, options); formSdkOptions(opts, options); formMigrateOptions(opts, options); @@ -204,16 +205,36 @@ function formCommandLineOptions(parsedCmd: ParsedCommand): CommandLineOptions { } function processRuleConfig(commandLineOptions: CommandLineOptions, options: OptionValues): void { - if (options.ruleConfig !== undefined) { - const stats = fs.statSync(path.normalize(options.ruleConfig)); - if (!stats.isFile()) { - console.error(`The file at ${options.ruleConfigPath} path does not exist!`); - } else { - const configuredRulesMap = getRulesFromConfig(options.ruleConfig); - const arkTSRulesMap = extractRuleTags(cookBookTag); - commandLineOptions.linterOptions.ruleConfigTags = getConfiguredRuleTags(arkTSRulesMap, configuredRulesMap); - } + if (options.ruleConfig === undefined) { + return; } + const stats = fs.statSync(path.normalize(options.ruleConfig)); + if (!stats.isFile()) { + Logger.error(`The file at ${options.ruleConfig} path does not exist!`); + return; + } + const configuredRulesMap = getRulesFromConfig(options.ruleConfig); + const arkTSRulesMap = extractRuleTags(cookBookTag); + commandLineOptions.linterOptions.ruleConfigTags = getConfiguredRuleTags(arkTSRulesMap, configuredRulesMap); +} + + +function processAutofixRuleConfig(commandLineOptions: CommandLineOptions, options: OptionValues): void { + if (options.ruleConfig) { + return; + } + const autofixConfigureRulePath = options.autofixRuleConfig; + if (!autofixConfigureRulePath || autofixConfigureRulePath.length == 0) { + return; + } + const stats = fs.statSync(path.normalize(options.autofixRuleConfig)); + if (!stats.isFile()) { + Logger.error(`The file at ${options.autofixRuleConfig} path does not exist!`); + return; + } + const configuredRulesMap = getRulesFromConfig(autofixConfigureRulePath); + const arkTSRulesMap = extractRuleTags(cookBookTag); + commandLineOptions.linterOptions.autofixRuleConfigTags = getConfiguredRuleTags(arkTSRulesMap, configuredRulesMap); } function createCommand(): Command { @@ -253,6 +274,7 @@ function createCommand(): Command { option('--verbose', 'set log level to see debug messages'). option('--enable-interop', 'scan whole project to report 1.1 import 1.2'). option('--rule-config ', 'Path to the rule configuration file'). + option('--autofix-rule-config ', 'Path to the autofix rule configuration file'). addOption(new Option('--warnings-as-errors', 'treat warnings as errors').hideHelp(true)). addOption(new Option('--no-check-ts-as-source', 'check TS files as third-party libary').hideHelp(true)). addOption(new Option('--no-use-rt-logic', 'run linter with SDK logic').hideHelp(true)). diff --git a/ets2panda/linter/src/lib/LinterOptions.ts b/ets2panda/linter/src/lib/LinterOptions.ts index 4c7e1d073e..88a6031780 100644 --- a/ets2panda/linter/src/lib/LinterOptions.ts +++ b/ets2panda/linter/src/lib/LinterOptions.ts @@ -48,4 +48,5 @@ export interface LinterOptions { inputFiles?: string[]; onlySyntax?: boolean; ruleConfigTags?: Set; + autofixRuleConfigTags?: Set; } diff --git a/ets2panda/linter/src/lib/LinterRunner.ts b/ets2panda/linter/src/lib/LinterRunner.ts index ea35b6ea9e..aa575487de 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,22 @@ 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 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) => { @@ -195,6 +195,27 @@ function migrate( return lintResult; } +function filterLinterProblemsWithAutofixConfig( + cmdOptions: CommandLineOptions, + problemsInfos: Map +): Map { + const autofixRuleConfigTags = cmdOptions.linterOptions.autofixRuleConfigTags; + if (!autofixRuleConfigTags) { + return problemsInfos; + } + + const needToBeFixedProblemsInfos = new Map(); + for (const [filePath, problems] of problemsInfos) { + const needToFix: ProblemInfo[] = problems.filter((problem) => { + return autofixRuleConfigTags.has(problem.ruleTag); + }); + if (needToFix.length > 0) { + needToBeFixedProblemsInfos.set(filePath, needToFix); + } + } + return needToBeFixedProblemsInfos; +} + function hasUseStaticDirective(srcFile: ts.SourceFile): boolean { if (!srcFile?.statements.length) { return false; @@ -217,7 +238,7 @@ function fix( let appliedFix = false; // Apply homecheck fixes first to avoid them being skipped due to conflict with linter autofixes let mergedProblems: Map = hcResults ?? new Map(); - mergedProblems = mergeArrayMaps(mergedProblems, lintResult.problemsInfos); + mergedProblems = mergeArrayMaps(mergedProblems, filterLinterProblemsWithAutofixConfig(linterConfig.cmdOptions, lintResult.problemsInfos)); mergedProblems.forEach((problemInfos, fileName) => { const srcFile = program.getSourceFile(fileName); if (!srcFile) { -- Gitee