diff --git a/ets2panda/linter/src/cli/CommandLineParser.ts b/ets2panda/linter/src/cli/CommandLineParser.ts index 9849eedf17ddf941595d7bb0021890af6a008f74..9a79cb343c5a94149f61f0ac0a5ffb3b0fe68990 100644 --- a/ets2panda/linter/src/cli/CommandLineParser.ts +++ b/ets2panda/linter/src/cli/CommandLineParser.ts @@ -22,6 +22,9 @@ import { Command, Option } from 'commander'; import * as ts from 'typescript'; import * as fs from 'node:fs'; import * as path from 'node:path'; +import { cookBookTag } from '../lib/CookBookMsg'; +import { getConfiguredRules, getConfiguredRuleTags } from '../lib/utils/functions/ConfiguredRuleFileProcess'; +import { extractArkTSRules } from '../lib/utils/functions/CooKBookProcesss'; const TS_EXT = '.ts'; const TSX_EXT = '.tsx'; @@ -190,6 +193,17 @@ function formCommandLineOptions(parsedCmd: ParsedCommand): CommandLineOptions { if (options.useRtLogic !== undefined) { opts.linterOptions.useRtLogic = options.useRtLogic; } + if (options.configRulePath !== undefined) { + opts.linterOptions.isConfigdRulePath = true; + const stats = fs.statSync(path.normalize(options.configRulePath)); + if (!stats.isFile()) { + console.error('The path configured rule of is not a real file'); + } else { + const configuredRulesMap = getConfiguredRules(options.configRulePath); + const arkTSRulesMap = extractArkTSRules(cookBookTag); + opts.linterOptions.configuredRuleTags = getConfiguredRuleTags(arkTSRulesMap, configuredRulesMap); + } + } formIdeInteractive(opts, options); formSdkOptions(opts, options); formMigrateOptions(opts, options); @@ -233,6 +247,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('--config-rule-path ', 'the rule path of manual configration'). 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 e1e433013c991aa8d5ee31af47b3f558ad34519e..b2ead73bdd595967be6caa5259a2d1360832cb00 100644 --- a/ets2panda/linter/src/lib/BaseTypeScriptLinter.ts +++ b/ets2panda/linter/src/lib/BaseTypeScriptLinter.ts @@ -75,13 +75,40 @@ export abstract class BaseTypeScriptLinter { autofix?: Autofix[], errorMsg?: string ): void { + const badNodeInfo = this.getbadNodeInfo(node, faultId, autofix, errorMsg); + + const linterOptions = this.options; + if (linterOptions.isConfigdRulePath && this.isSkipeConfigdRule(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 isSkipeConfigdRule(badNodeInfo: ProblemInfo): boolean { + const configuredRuleTags = this.options.configuredRuleTags; + if (configuredRuleTags?.has(badNodeInfo.ruleTag)) { + return false; + } + return true; + } + + 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; @@ -106,12 +133,6 @@ export abstract class BaseTypeScriptLinter { autofix: autofix, autofixTitle: isMsgNumValid && autofix !== undefined ? cookBookRefToFixTitle.get(cookBookMsgNum) : undefined }; - 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; } } diff --git a/ets2panda/linter/src/lib/CommandLineOptions.ts b/ets2panda/linter/src/lib/CommandLineOptions.ts index 1bdde46faeaa7c3be16eb3717c3f666ed2658765..3d5e0899cc6868beff3e543ddce3363900c2fd7d 100644 --- a/ets2panda/linter/src/lib/CommandLineOptions.ts +++ b/ets2panda/linter/src/lib/CommandLineOptions.ts @@ -34,4 +34,5 @@ export interface CommandLineOptions { outputFilePath?: string; verbose?: boolean; scanWholeProjectInHomecheck?: boolean; + configRulePath?: string; } diff --git a/ets2panda/linter/src/lib/LinterOptions.ts b/ets2panda/linter/src/lib/LinterOptions.ts index 72613a4cd1648f8febcc6e49c1263e7d21883b29..d36f2321c083659aa6bd7220fcae0fcbe427b63e 100644 --- a/ets2panda/linter/src/lib/LinterOptions.ts +++ b/ets2panda/linter/src/lib/LinterOptions.ts @@ -46,4 +46,6 @@ export interface LinterOptions { wholeProjectPath?: string; checkTsAndJs?: boolean; inputFiles?: string[]; + isConfigdRulePath?: boolean; + configuredRuleTags?: Set; } diff --git a/ets2panda/linter/src/lib/utils/consts/ArkTS2Rules.ts b/ets2panda/linter/src/lib/utils/consts/ArkTS2Rules.ts index 1f26e36e862aa393385cf635d5b726a41ec9c02c..5ae8841d8bf09fed465f58d44e7876383a608bc3 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/ConfiguredRuleFileProcess.ts b/ets2panda/linter/src/lib/utils/functions/ConfiguredRuleFileProcess.ts new file mode 100644 index 0000000000000000000000000000000000000000..731b0812694ea5a0bfbc208366c53d2638256df9 --- /dev/null +++ b/ets2panda/linter/src/lib/utils/functions/ConfiguredRuleFileProcess.ts @@ -0,0 +1,79 @@ +/* + * 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 mergedRules: string[] = Array.from(configuredRulesMap.values()).flat(); + + const configuredRuleTags = new Set(); + + if (arkTSRulesMap) { + const valueToKeyMap = new Map(); + + for (const [key, value] of arkTSRulesMap) { + valueToKeyMap.set(value, key); + } + + const mergedRulesSet = new Set(mergedRules); + + valueToKeyMap.forEach((key, value) => { + if (mergedRulesSet.has(value)) { + configuredRuleTags.add(key); + } + }); + } + return configuredRuleTags; +} + +export 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 0000000000000000000000000000000000000000..6f17b4ee8ab407356598cbca5ccc733067fac086 --- /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; +}