From 97186f69b0c16590b3f70414d3b517ae16e942a7 Mon Sep 17 00:00:00 2001 From: ohhusenlin Date: Sat, 21 Jun 2025 20:39:53 +0800 Subject: [PATCH] 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 | 3 + .../linter/src/lib/BaseTypeScriptLinter.ts | 58 ++++++++----- .../linter/src/lib/CommandLineOptions.ts | 1 + ets2panda/linter/src/lib/LinterOptions.ts | 2 + .../src/lib/utils/consts/ArkTS2Rules.ts | 14 ++- .../utils/functions/ConfiguredRulesProcess.ts | 86 +++++++++++++++++++ .../lib/utils/functions/CooKBookProcesss.ts | 35 ++++++++ 7 files changed, 178 insertions(+), 21 deletions(-) create mode 100644 ets2panda/linter/src/lib/utils/functions/ConfiguredRulesProcess.ts create mode 100644 ets2panda/linter/src/lib/utils/functions/CooKBookProcesss.ts diff --git a/ets2panda/linter/src/cli/CommandLineParser.ts b/ets2panda/linter/src/cli/CommandLineParser.ts index 42e2cf404b..ca0f4f24ef 100644 --- a/ets2panda/linter/src/cli/CommandLineParser.ts +++ b/ets2panda/linter/src/cli/CommandLineParser.ts @@ -22,6 +22,7 @@ import { Command, Option } from 'commander'; import * as ts from 'typescript'; import * as fs from 'node:fs'; import * as path from 'node:path'; +import { processConfiguredRules } from '../lib/utils/functions/ConfiguredRulesProcess'; const TS_EXT = '.ts'; const TSX_EXT = '.tsx'; @@ -192,6 +193,7 @@ function formCommandLineOptions(parsedCmd: ParsedCommand): CommandLineOptions { if (options.useRtLogic !== undefined) { opts.linterOptions.useRtLogic = options.useRtLogic; } + processConfiguredRules(opts, options); formIdeInteractive(opts, options); formSdkOptions(opts, options); formMigrateOptions(opts, options); @@ -235,6 +237,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('--configure-rule-path ', 'the rule path of manual configuration'). 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..35ac468011 100644 --- a/ets2panda/linter/src/lib/BaseTypeScriptLinter.ts +++ b/ets2panda/linter/src/lib/BaseTypeScriptLinter.ts @@ -76,13 +76,36 @@ export abstract class BaseTypeScriptLinter { autofix?: Autofix[], errorMsg?: string ): void { + const badNodeInfo = this.getbadNodeInfo(node, faultId, autofix, errorMsg); + + const linterOptions = this.options; + if (linterOptions.isConfiguredRulePath) { + if (this.isSkipeConfigdRule(badNodeInfo)) { + return; + } + } else 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); + } + } + + 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 +130,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( @@ -129,15 +142,20 @@ export abstract class BaseTypeScriptLinter { } private isSkipedRecordProblems(badNodeInfo: ProblemInfo): boolean { - if (this.options.onlySyntax) { - if (onlyArkts2SyntaxRules.has(badNodeInfo.ruleTag)) { - return false; - } - } else { - if (this.options.arkts2 - && arkts2Rules.includes(badNodeInfo.ruleTag)) { + if (this.options.onlySyntax) { + if (onlyArkts2SyntaxRules.has(badNodeInfo.ruleTag)) { return false; } + } else if (this.options.arkts2 && arkts2Rules.includes(badNodeInfo.ruleTag)) { + return false; + } + return true; + } + + private isSkipeConfigdRule(badNodeInfo: ProblemInfo): boolean { + const configuredRuleTags = this.options.configuredRuleTags; + if (configuredRuleTags?.has(badNodeInfo.ruleTag)) { + return false; } return true; } diff --git a/ets2panda/linter/src/lib/CommandLineOptions.ts b/ets2panda/linter/src/lib/CommandLineOptions.ts index d793e62044..c58432c709 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; + configureRulePath?: string; } diff --git a/ets2panda/linter/src/lib/LinterOptions.ts b/ets2panda/linter/src/lib/LinterOptions.ts index 11c2183d5c..26598d8163 100644 --- a/ets2panda/linter/src/lib/LinterOptions.ts +++ b/ets2panda/linter/src/lib/LinterOptions.ts @@ -47,4 +47,6 @@ export interface LinterOptions { checkTsAndJs?: boolean; inputFiles?: string[]; onlySyntax?: boolean; + isConfiguredRulePath?: boolean; + configuredRuleTags?: Set; } diff --git a/ets2panda/linter/src/lib/utils/consts/ArkTS2Rules.ts b/ets2panda/linter/src/lib/utils/consts/ArkTS2Rules.ts index 1f26e36e86..5ae8841d8b 100644 --- a/ets2panda/linter/src/lib/utils/consts/ArkTS2Rules.ts +++ b/ets2panda/linter/src/lib/utils/consts/ArkTS2Rules.ts @@ -193,5 +193,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..8583b6cc5e --- /dev/null +++ b/ets2panda/linter/src/lib/utils/functions/ConfiguredRulesProcess.ts @@ -0,0 +1,86 @@ +/* + * 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 type { OptionValues } from 'commander'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import type { CommandLineOptions } from '../../CommandLineOptions'; +import { cookBookTag } from '../../CookBookMsg'; +import { extractArkTSRules } from './CooKBookProcesss'; + +export function processConfiguredRules(commandLineOptions: CommandLineOptions, options: OptionValues): void { + if (options.configureRulePath !== undefined) { + commandLineOptions.linterOptions.isConfiguredRulePath = true; + const stats = fs.statSync(path.normalize(options.configureRulePath)); + if (!stats.isFile()) { + console.error('The path configured rule of is not a real file'); + } else { + const configuredRulesMap = getConfiguredRules(options.configureRulePath); + const arkTSRulesMap = extractArkTSRules(cookBookTag); + commandLineOptions.linterOptions.configuredRuleTags = getConfiguredRuleTags(arkTSRulesMap, configuredRulesMap); + } + } +} + +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; +} + +function getConfiguredRules(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 prase 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/CooKBookProcesss.ts b/ets2panda/linter/src/lib/utils/functions/CooKBookProcesss.ts new file mode 100644 index 0000000000..6f17b4ee8a --- /dev/null +++ b/ets2panda/linter/src/lib/utils/functions/CooKBookProcesss.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 extractArkTSRules(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