From 525c49bd4b5c1fed1796493343c5e19deda5d52d Mon Sep 17 00:00:00 2001 From: bulutcan99 Date: Mon, 11 Aug 2025 19:52:02 +0300 Subject: [PATCH 1/6] feat: added node validator for use cases like try, comparison, undefined --- .../fast_build/system_api/api_check_utils.ts | 34 ++-- .../fast_build/system_api/node_validator.ts | 156 ++++++++++++++++++ 2 files changed, 176 insertions(+), 14 deletions(-) create mode 100644 compiler/src/fast_build/system_api/node_validator.ts diff --git a/compiler/src/fast_build/system_api/api_check_utils.ts b/compiler/src/fast_build/system_api/api_check_utils.ts index 71be695cd..61ba7071e 100644 --- a/compiler/src/fast_build/system_api/api_check_utils.ts +++ b/compiler/src/fast_build/system_api/api_check_utils.ts @@ -73,6 +73,7 @@ import { VERSION_CHECK_FUNCTION_NAME } from './api_check_define'; import { JsDocCheckService } from './api_check_permission'; +import {NodeValidator} from "./node_validator"; /** * bundle info @@ -89,7 +90,7 @@ export interface CheckValidCallbackInterface { } export interface CheckJsDocSpecialValidCallbackInterface { - (jsDocTags: readonly ts.JSDocTag[], config: ts.JsDocNodeCheckConfigItem): boolean; + (node: ts.CallExpression, jsDocTags: readonly ts.JSDocTag[], config: ts.JsDocNodeCheckConfigItem): boolean; } export interface checkConditionValidCallbackInterface { @@ -546,12 +547,12 @@ function collectExternalSyscapInfos( /** * Determine the necessity of since check. - * - * @param jsDocTags - * @param config - * @returns + * + * @param jsDocTags + * @param config + * @returns */ -function checkSinceValue(jsDocTags: readonly ts.JSDocTag[], config: ts.JsDocNodeCheckConfigItem): boolean { +function checkSinceValue(node: ts.CallExpression, jsDocTags: readonly ts.JSDocTag[], config: ts.JsDocNodeCheckConfigItem): boolean { if (!jsDocTags[0]?.parent?.parent || !projectConfig.compatibleSdkVersion) { return false; } @@ -567,6 +568,11 @@ function checkSinceValue(jsDocTags: readonly ts.JSDocTag[], config: ts.JsDocNode return false; } if (hasSince && comparePointVersion(compatibleSdkVersion.toString(), minSince) === -1) { + const nodeValidator = new NodeValidator(compatibleSdkVersion); + + if (nodeValidator.isNodeHandled(node)) { + return false; + } config.message = SINCE_TAG_CHECK_ERROER.replace('$SINCE1', minSince).replace('$SINCE2', compatibleSdkVersion); return true; } @@ -581,7 +587,7 @@ function checkSinceValue(jsDocTags: readonly ts.JSDocTag[], config: ts.JsDocNode * the major version be from 1-999 * the minor version be from 0-999 * the patch version be from 0-999 - * + * * @param {string} since * @return {boolean} */ @@ -591,9 +597,9 @@ function isCompliantSince(since: string): boolean { /** * Determine the necessity of syscap check. - * @param jsDocTags - * @param config - * @returns + * @param jsDocTags + * @param config + * @returns */ export function checkSyscapAbility(jsDocTags: readonly ts.JSDocTag[], config: ts.JsDocNodeCheckConfigItem): boolean { let currentSyscapValue: string = ''; @@ -783,7 +789,7 @@ function checkSyscapConditionValidCallback(node: ts.CallExpression, specifyFuncN /** * get minversion - * @param { ts.JSDoc[] } jsDocs + * @param { ts.JSDoc[] } jsDocs * @returns string */ function getMinVersion(jsDocs: ts.JSDoc[]): string { @@ -805,8 +811,8 @@ function getMinVersion(jsDocs: ts.JSDoc[]): string { /** * compare point version - * @param { string } firstVersion - * @param { string } secondVersion + * @param { string } firstVersion + * @param { string } secondVersion * @returns { number } */ function comparePointVersion(firstVersion: string, secondVersion: string): number { @@ -824,4 +830,4 @@ function comparePointVersion(firstVersion: string, secondVersion: string): numbe } } return 0; -} \ No newline at end of file +} diff --git a/compiler/src/fast_build/system_api/node_validator.ts b/compiler/src/fast_build/system_api/node_validator.ts new file mode 100644 index 000000000..42f8198c1 --- /dev/null +++ b/compiler/src/fast_build/system_api/node_validator.ts @@ -0,0 +1,156 @@ +import ts from 'typescript'; + +export class NodeValidator { + private readonly devEcoVersionApis: string[] = ['sdkApi.getVersion()']; + private readonly compatibleSdkVersion: string; + + constructor(projectCompatibleSdkVersion: string) { + this.compatibleSdkVersion = projectCompatibleSdkVersion; + } + + /** + * Checks whether a given node is valid for at least one condition. + * @param node The node to check (e.g., a function call). + * @returns {boolean} True if the node is within a 'try' block. + */ + public isNodeHandled(node: ts.Node): boolean { + return ( + this.isNodeWrappedInTryCatch(node) || + this.isNodeWrappedInSdkComparison(node) || + this.isNodeWrappedInUndefinedCheck(node) + ); + } + + public isNodeWrappedInTryCatch(node: ts.Node): boolean { + let currentNode = node.parent; + while (currentNode) { + if (ts.isTryStatement(currentNode)) { + if (node.getStart() >= currentNode.tryBlock.getStart()) { + return true; + } + } + currentNode = currentNode.parent; + } + return false; + } + + public isNodeWrappedInUndefinedCheck(node: ts.Node): boolean { + const targetName = this.getPrimaryNameFromNode(node); + if (!targetName) { + return false; + } + + let currentNode = node.parent; + while (currentNode) { + if (ts.isIfStatement(currentNode)) { + if (this.isUndefinedCheckHelper(currentNode.expression, targetName)) { + return true; + } + } + currentNode = currentNode.parent; + } + return false; + } + + public isNodeWrappedInSdkComparison(node: ts.Node): boolean { + if (this.compatibleSdkVersion === '') { + return false; + } + let currentNode = node.parent; + while (currentNode) { + if (ts.isIfStatement(currentNode)) { + if (this.isSdkComparisonHelper(currentNode.expression)) { + return true; + } + } + currentNode = currentNode.parent; + } + return false; + } + + private getPrimaryNameFromNode(node: ts.Node): string | undefined { + if (ts.isIdentifier(node)) { + return node.text; + } + if (ts.isCallExpression(node)) { + return this.getPrimaryNameFromNode(node.expression); + } + if (ts.isPropertyAccessExpression(node)) { + return node.name.text; + } + return undefined; + } + + private isUndefinedCheckHelper(expression: ts.Expression, name: string): boolean { + if (!ts.isBinaryExpression(expression)) { + return false; + } + + const allowedOperators = new Set([ + ts.SyntaxKind.ExclamationEqualsEqualsToken, + ts.SyntaxKind.ExclamationEqualsToken + ]); + if (!allowedOperators.has(expression.operatorToken.kind)) { + return false; + } + + const isUndefinedNode = (node: ts.Node): boolean => + ts.isIdentifier(node) && node.text === 'undefined'; + + const isTargetNode = (node: ts.Node): boolean => { + const nodePrimaryName = this.getPrimaryNameFromNode(node); + return nodePrimaryName === name; + }; + + const { left, right } = expression; + const case1 = isTargetNode(left) && isUndefinedNode(right); + const case2 = isUndefinedNode(left) && isTargetNode(right); + + return case1 || case2; + } + + private isSdkComparisonHelper(expression: ts.Expression): boolean { + if (!ts.isBinaryExpression(expression)) { + return false; + } + + const { left, right } = expression; + const isApiLeftAndNumberRight = this.isDevEcoVersionApi(left) && ts.isNumericLiteral(right); + const isNumberLeftAndApiRight = ts.isNumericLiteral(left) && this.isDevEcoVersionApi(right); + + return isApiLeftAndNumberRight || isNumberLeftAndApiRight; + } + + private isDevEcoVersionApi(node: ts.Expression): boolean { + if (!ts.isCallExpression(node)) { + return false; + } + const apiCallString = this.buildApiCallString(node); + if (!apiCallString) { + return false; + } + return this.devEcoVersionApis.includes(apiCallString); + } + + private buildApiCallString(callExpression: ts.CallExpression): string | null { + if (ts.isPropertyAccessExpression(callExpression.expression)) { + const objectChain = this.buildObjectChain(callExpression.expression); + if (objectChain && callExpression.arguments.length === 0) { + return `${objectChain}()`; + } + } + return null; + } + + private buildObjectChain(node: ts.PropertyAccessExpression): string | null { + const propertyName = node.name.text; + if (ts.isIdentifier(node.expression)) { + const objectName = node.expression.text; + return `${objectName}.${propertyName}`; + } else if (ts.isPropertyAccessExpression(node.expression)) { + const parentChain = this.buildObjectChain(node.expression); + return parentChain ? `${parentChain}.${propertyName}` : null; + } + return null; + } +} -- Gitee From 5c63c699fe7305799ab53e8c823f35933c8d495c Mon Sep 17 00:00:00 2001 From: bulutcan99 Date: Mon, 11 Aug 2025 22:46:43 +0300 Subject: [PATCH 2/6] refactor: update for ts.Node --- compiler/src/fast_build/system_api/api_check_utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/fast_build/system_api/api_check_utils.ts b/compiler/src/fast_build/system_api/api_check_utils.ts index 61ba7071e..65d0e1ea9 100644 --- a/compiler/src/fast_build/system_api/api_check_utils.ts +++ b/compiler/src/fast_build/system_api/api_check_utils.ts @@ -90,7 +90,7 @@ export interface CheckValidCallbackInterface { } export interface CheckJsDocSpecialValidCallbackInterface { - (node: ts.CallExpression, jsDocTags: readonly ts.JSDocTag[], config: ts.JsDocNodeCheckConfigItem): boolean; + (node: ts.Node, jsDocTags: readonly ts.JSDocTag[], config: ts.JsDocNodeCheckConfigItem): boolean; } export interface checkConditionValidCallbackInterface { @@ -552,7 +552,7 @@ function collectExternalSyscapInfos( * @param config * @returns */ -function checkSinceValue(node: ts.CallExpression, jsDocTags: readonly ts.JSDocTag[], config: ts.JsDocNodeCheckConfigItem): boolean { +function checkSinceValue(node: ts.Node, jsDocTags: readonly ts.JSDocTag[], config: ts.JsDocNodeCheckConfigItem): boolean { if (!jsDocTags[0]?.parent?.parent || !projectConfig.compatibleSdkVersion) { return false; } -- Gitee From d26996695770a3a02b064c5b2c9dafbe5cf6aa23 Mon Sep 17 00:00:00 2001 From: bulutcan99 Date: Tue, 12 Aug 2025 21:56:41 +0300 Subject: [PATCH 3/6] refactor: update for sdk comparison --- .../fast_build/system_api/api_check_utils.ts | 8 +- .../fast_build/system_api/node_validator.ts | 159 +++++++++++++----- 2 files changed, 119 insertions(+), 48 deletions(-) diff --git a/compiler/src/fast_build/system_api/api_check_utils.ts b/compiler/src/fast_build/system_api/api_check_utils.ts index 65d0e1ea9..3dc86b2a4 100644 --- a/compiler/src/fast_build/system_api/api_check_utils.ts +++ b/compiler/src/fast_build/system_api/api_check_utils.ts @@ -90,7 +90,7 @@ export interface CheckValidCallbackInterface { } export interface CheckJsDocSpecialValidCallbackInterface { - (node: ts.Node, jsDocTags: readonly ts.JSDocTag[], config: ts.JsDocNodeCheckConfigItem): boolean; + (jsDocTags: readonly ts.JSDocTag[], config: ts.JsDocNodeCheckConfigItem, node?: ts.Node): boolean; } export interface checkConditionValidCallbackInterface { @@ -550,9 +550,10 @@ function collectExternalSyscapInfos( * * @param jsDocTags * @param config + * @param node * @returns */ -function checkSinceValue(node: ts.Node, jsDocTags: readonly ts.JSDocTag[], config: ts.JsDocNodeCheckConfigItem): boolean { +function checkSinceValue(jsDocTags: readonly ts.JSDocTag[], config: ts.JsDocNodeCheckConfigItem, node?: ts.Node): boolean { if (!jsDocTags[0]?.parent?.parent || !projectConfig.compatibleSdkVersion) { return false; } @@ -568,7 +569,8 @@ function checkSinceValue(node: ts.Node, jsDocTags: readonly ts.JSDocTag[], confi return false; } if (hasSince && comparePointVersion(compatibleSdkVersion.toString(), minSince) === -1) { - const nodeValidator = new NodeValidator(compatibleSdkVersion); + const checker: ts.TypeChecker | undefined = CurrentProcessFile.getChecker(); + const nodeValidator = new NodeValidator(compatibleSdkVersion, checker); if (nodeValidator.isNodeHandled(node)) { return false; diff --git a/compiler/src/fast_build/system_api/node_validator.ts b/compiler/src/fast_build/system_api/node_validator.ts index 42f8198c1..165c181b0 100644 --- a/compiler/src/fast_build/system_api/node_validator.ts +++ b/compiler/src/fast_build/system_api/node_validator.ts @@ -1,11 +1,18 @@ import ts from 'typescript'; +import {CurrentProcessFile} from "../../utils"; export class NodeValidator { - private readonly devEcoVersionApis: string[] = ['sdkApi.getVersion()']; + private readonly sdkApiToPackageMap: Map = new Map([ + ['deviceInfo.sdkApiVersion', ['node_modules/@infolib', 'node_modules/@infoDialib']], + // Future: ['systemInfo.apiLevel', ['node_modules/@harmonylib']] + ]); + private readonly compatibleSdkVersion: string; + private readonly typeChecker?: ts.TypeChecker; - constructor(projectCompatibleSdkVersion: string) { - this.compatibleSdkVersion = projectCompatibleSdkVersion; + constructor(projectCompatibleSdkVersion: string, typeChecker?: ts.TypeChecker) { + this.compatibleSdkVersion = projectCompatibleSdkVersion; + this.typeChecker = typeChecker; } /** @@ -52,22 +59,6 @@ export class NodeValidator { return false; } - public isNodeWrappedInSdkComparison(node: ts.Node): boolean { - if (this.compatibleSdkVersion === '') { - return false; - } - let currentNode = node.parent; - while (currentNode) { - if (ts.isIfStatement(currentNode)) { - if (this.isSdkComparisonHelper(currentNode.expression)) { - return true; - } - } - currentNode = currentNode.parent; - } - return false; - } - private getPrimaryNameFromNode(node: ts.Node): string | undefined { if (ts.isIdentifier(node)) { return node.text; @@ -109,48 +100,126 @@ export class NodeValidator { return case1 || case2; } + public isNodeWrappedInSdkComparison(node: ts.Node): boolean { + if (this.compatibleSdkVersion === '') { + return false; + } + + let currentNode = node.parent; + while (currentNode) { + if (ts.isIfStatement(currentNode)) { + if (this.isSdkComparisonHelper(currentNode.expression)) { + return true; + } + } + currentNode = currentNode.parent; + } + return false; + } + private isSdkComparisonHelper(expression: ts.Expression): boolean { - if (!ts.isBinaryExpression(expression)) { + const expressionText = expression.getText(); + const matchedApiEntry = Array.from(this.sdkApiToPackageMap.entries()) + .find(([api]) => expressionText.includes(api)); + + if (!matchedApiEntry) { return false; } - const { left, right } = expression; - const isApiLeftAndNumberRight = this.isDevEcoVersionApi(left) && ts.isNumericLiteral(right); - const isNumberLeftAndApiRight = ts.isNumericLiteral(left) && this.isDevEcoVersionApi(right); + const [matchedApi, validPackagePaths] = matchedApiEntry; - return isApiLeftAndNumberRight || isNumberLeftAndApiRight; + if (this.typeChecker) { + const apiIdentifier = this.findApiIdentifier(expression, matchedApi); + if (!apiIdentifier) { + return false; + } + return this.isValidSdkDeclaration(apiIdentifier, validPackagePaths); + } + + // Fallback for when TypeChecker is not available + return this.hasValidImportForApi(expression.getSourceFile(), validPackagePaths); } - private isDevEcoVersionApi(node: ts.Expression): boolean { - if (!ts.isCallExpression(node)) { - return false; + private findApiIdentifier(expression: ts.Expression, api: string): ts.Identifier | undefined { + const apiParts = api.split('.'); + if (apiParts.length < 2) return undefined; + + const rootPropertyName = apiParts[0]; + + if (ts.isBinaryExpression(expression)) { + const leftId = this.extractApiIdentifierFromExpression(expression.left, rootPropertyName); + if (leftId) return leftId; + + const rightId = this.extractApiIdentifierFromExpression(expression.right, rootPropertyName); + if (rightId) return rightId; + } + + return this.extractApiIdentifierFromExpression(expression, rootPropertyName); + } + + private extractApiIdentifierFromExpression(expression: ts.Expression, rootPropertyName: string): ts.Identifier | undefined { + if (ts.isPropertyAccessExpression(expression)) { + let current = expression; + while (ts.isPropertyAccessExpression(current)) { + if (current.name.text === rootPropertyName.split('.').pop()) { + if (ts.isPropertyAccessExpression(current.expression) && + current.expression.name.text === rootPropertyName.split('.')[0]) { + return this.getRootIdentifier(current.expression.expression); + } + } + current = current.expression as ts.PropertyAccessExpression; + } } - const apiCallString = this.buildApiCallString(node); - if (!apiCallString) { + return undefined; + } + + private isValidSdkDeclaration(identifier: ts.Identifier, validPackagePaths: string[]): boolean { + if (!this.typeChecker) return false; + + const symbol = this.typeChecker.getSymbolAtLocation(identifier); + if (!symbol || !symbol.declarations || symbol.declarations.length === 0) { return false; } - return this.devEcoVersionApis.includes(apiCallString); + + const declarationFile = symbol.declarations[0].getSourceFile().fileName; + return this.isValidSdkDeclarationPath(declarationFile, validPackagePaths); + } + + private isValidSdkDeclarationPath(filePath: string, validPackagePaths: string[]): boolean { + const normalizedPath = this.normalizePath(filePath); + return validPackagePaths.some(validPath => + normalizedPath.includes(this.normalizePath(validPath)) + ); } - private buildApiCallString(callExpression: ts.CallExpression): string | null { - if (ts.isPropertyAccessExpression(callExpression.expression)) { - const objectChain = this.buildObjectChain(callExpression.expression); - if (objectChain && callExpression.arguments.length === 0) { - return `${objectChain}()`; + private hasValidImportForApi(sourceFile: ts.SourceFile, validPackagePaths: string[]): boolean { + for (const statement of sourceFile.statements) { + if (ts.isImportDeclaration(statement)) { + const moduleSpecifier = statement.moduleSpecifier; + if (ts.isStringLiteral(moduleSpecifier)) { + const importPath = moduleSpecifier.text; + if (validPackagePaths.some(validPath => + this.normalizePath(importPath).includes(this.normalizePath(validPath.replace('node_modules/', ''))))) { + return true; + } + } } } - return null; + return false; } - private buildObjectChain(node: ts.PropertyAccessExpression): string | null { - const propertyName = node.name.text; - if (ts.isIdentifier(node.expression)) { - const objectName = node.expression.text; - return `${objectName}.${propertyName}`; - } else if (ts.isPropertyAccessExpression(node.expression)) { - const parentChain = this.buildObjectChain(node.expression); - return parentChain ? `${parentChain}.${propertyName}` : null; + private getRootIdentifier(expression: ts.Expression): ts.Identifier | undefined { + if (ts.isIdentifier(expression)) { + return expression; + } + if (ts.isPropertyAccessExpression(expression)) { + return this.getRootIdentifier(expression.expression); } - return null; + return undefined; + } + + private normalizePath(path: string): string { + return path.replace(/\\/g, '/').toLowerCase(); } + } -- Gitee From f8b529320d1d51e3faa8eab6550fd90449678525 Mon Sep 17 00:00:00 2001 From: bulutcan99 Date: Wed, 13 Aug 2025 20:27:16 +0300 Subject: [PATCH 4/6] refactor: updated sdk comparison --- .../fast_build/system_api/node_validator.ts | 156 +++++++++--------- 1 file changed, 80 insertions(+), 76 deletions(-) diff --git a/compiler/src/fast_build/system_api/node_validator.ts b/compiler/src/fast_build/system_api/node_validator.ts index 165c181b0..f6487acf8 100644 --- a/compiler/src/fast_build/system_api/node_validator.ts +++ b/compiler/src/fast_build/system_api/node_validator.ts @@ -1,10 +1,8 @@ import ts from 'typescript'; -import {CurrentProcessFile} from "../../utils"; export class NodeValidator { private readonly sdkApiToPackageMap: Map = new Map([ - ['deviceInfo.sdkApiVersion', ['node_modules/@infolib', 'node_modules/@infoDialib']], - // Future: ['systemInfo.apiLevel', ['node_modules/@harmonylib']] + ['.sdkApiVersion', ['@ohos.deviceInfo.d.ts']], ]); private readonly compatibleSdkVersion: string; @@ -23,8 +21,8 @@ export class NodeValidator { public isNodeHandled(node: ts.Node): boolean { return ( this.isNodeWrappedInTryCatch(node) || - this.isNodeWrappedInSdkComparison(node) || - this.isNodeWrappedInUndefinedCheck(node) + this.isNodeWrappedInUndefinedCheck(node) || + this.isNodeWrappedInSdkComparison(node) ); } @@ -59,6 +57,24 @@ export class NodeValidator { return false; } + + public isNodeWrappedInSdkComparison(node: ts.Node): boolean { + if (this.compatibleSdkVersion === '') { + return false; + } + + let currentNode = node.parent; + while (currentNode) { + if (ts.isIfStatement(currentNode)) { + if (this.isSdkComparisonHelper(currentNode.expression)) { + return true; + } + } + currentNode = currentNode.parent; + } + return false; + } + private getPrimaryNameFromNode(node: ts.Node): string | undefined { if (ts.isIdentifier(node)) { return node.text; @@ -100,23 +116,6 @@ export class NodeValidator { return case1 || case2; } - public isNodeWrappedInSdkComparison(node: ts.Node): boolean { - if (this.compatibleSdkVersion === '') { - return false; - } - - let currentNode = node.parent; - while (currentNode) { - if (ts.isIfStatement(currentNode)) { - if (this.isSdkComparisonHelper(currentNode.expression)) { - return true; - } - } - currentNode = currentNode.parent; - } - return false; - } - private isSdkComparisonHelper(expression: ts.Expression): boolean { const expressionText = expression.getText(); const matchedApiEntry = Array.from(this.sdkApiToPackageMap.entries()) @@ -128,48 +127,63 @@ export class NodeValidator { const [matchedApi, validPackagePaths] = matchedApiEntry; - if (this.typeChecker) { - const apiIdentifier = this.findApiIdentifier(expression, matchedApi); - if (!apiIdentifier) { - return false; - } - return this.isValidSdkDeclaration(apiIdentifier, validPackagePaths); + if (!this.typeChecker) { + return false; } - // Fallback for when TypeChecker is not available - return this.hasValidImportForApi(expression.getSourceFile(), validPackagePaths); + const apiIdentifier = this.findApiIdentifier(expression, matchedApi); + if (!apiIdentifier) { + return false; + } + + try { + return this.isValidSdkDeclaration(apiIdentifier, validPackagePaths); + } catch (err) { + return false; + } } private findApiIdentifier(expression: ts.Expression, api: string): ts.Identifier | undefined { const apiParts = api.split('.'); if (apiParts.length < 2) return undefined; - const rootPropertyName = apiParts[0]; + const propertyName = apiParts[0]; + const finalProperty = apiParts[1]; if (ts.isBinaryExpression(expression)) { - const leftId = this.extractApiIdentifierFromExpression(expression.left, rootPropertyName); + const leftId = this.extractApiIdentifierFromExpression(expression.left, finalProperty); if (leftId) return leftId; - const rightId = this.extractApiIdentifierFromExpression(expression.right, rootPropertyName); + const rightId = this.extractApiIdentifierFromExpression(expression.right, finalProperty); if (rightId) return rightId; } - return this.extractApiIdentifierFromExpression(expression, rootPropertyName); + return this.extractApiIdentifierFromExpression(expression, finalProperty); } - private extractApiIdentifierFromExpression(expression: ts.Expression, rootPropertyName: string): ts.Identifier | undefined { - if (ts.isPropertyAccessExpression(expression)) { - let current = expression; - while (ts.isPropertyAccessExpression(current)) { - if (current.name.text === rootPropertyName.split('.').pop()) { - if (ts.isPropertyAccessExpression(current.expression) && - current.expression.name.text === rootPropertyName.split('.')[0]) { - return this.getRootIdentifier(current.expression.expression); - } - } - current = current.expression as ts.PropertyAccessExpression; - } + private extractApiIdentifierFromExpression( + expression: ts.Expression, + finalProperty: string + ): ts.Identifier | undefined { + + if (!ts.isPropertyAccessExpression(expression)) { + return undefined; } + + if (expression.name.text !== finalProperty) { + return undefined; + } + + let current: ts.Expression = expression.expression; + + while (ts.isPropertyAccessExpression(current)) { + current = current.expression; + } + + if (ts.isIdentifier(current)) { + return current; + } + return undefined; } @@ -177,12 +191,29 @@ export class NodeValidator { if (!this.typeChecker) return false; const symbol = this.typeChecker.getSymbolAtLocation(identifier); - if (!symbol || !symbol.declarations || symbol.declarations.length === 0) { + if (!symbol) { return false; } - const declarationFile = symbol.declarations[0].getSourceFile().fileName; - return this.isValidSdkDeclarationPath(declarationFile, validPackagePaths); + const actualDeclarationFile = this.getActualDeclarationFile(symbol); + if (!actualDeclarationFile) { + return false; + } + + return this.isValidSdkDeclarationPath(actualDeclarationFile, validPackagePaths); + } + + private getActualDeclarationFile(symbol: ts.Symbol): string | undefined { + const aliasedSymbol = this.typeChecker!.getAliasedSymbol(symbol); + const targetSymbol = aliasedSymbol !== symbol ? aliasedSymbol : symbol; + + if (!targetSymbol.declarations || targetSymbol.declarations.length === 0) { + return undefined; + } + + const declarationFile = targetSymbol.declarations[0].getSourceFile().fileName; + const relativePath = declarationFile.replace(/^.*sdk[\\/].*[\\/](?=@)/, ''); + return relativePath; } private isValidSdkDeclarationPath(filePath: string, validPackagePaths: string[]): boolean { @@ -192,34 +223,7 @@ export class NodeValidator { ); } - private hasValidImportForApi(sourceFile: ts.SourceFile, validPackagePaths: string[]): boolean { - for (const statement of sourceFile.statements) { - if (ts.isImportDeclaration(statement)) { - const moduleSpecifier = statement.moduleSpecifier; - if (ts.isStringLiteral(moduleSpecifier)) { - const importPath = moduleSpecifier.text; - if (validPackagePaths.some(validPath => - this.normalizePath(importPath).includes(this.normalizePath(validPath.replace('node_modules/', ''))))) { - return true; - } - } - } - } - return false; - } - - private getRootIdentifier(expression: ts.Expression): ts.Identifier | undefined { - if (ts.isIdentifier(expression)) { - return expression; - } - if (ts.isPropertyAccessExpression(expression)) { - return this.getRootIdentifier(expression.expression); - } - return undefined; - } - private normalizePath(path: string): string { return path.replace(/\\/g, '/').toLowerCase(); } - } -- Gitee From 40c05fe447f34b7eaab3e2ce3c32e74a6087b2aa Mon Sep 17 00:00:00 2001 From: bulutcan99 Date: Wed, 13 Aug 2025 20:52:45 +0300 Subject: [PATCH 5/6] refactor: more readable nodeValidator --- .../fast_build/system_api/node_validator.ts | 182 +++++++++--------- 1 file changed, 91 insertions(+), 91 deletions(-) diff --git a/compiler/src/fast_build/system_api/node_validator.ts b/compiler/src/fast_build/system_api/node_validator.ts index f6487acf8..4069463d4 100644 --- a/compiler/src/fast_build/system_api/node_validator.ts +++ b/compiler/src/fast_build/system_api/node_validator.ts @@ -2,7 +2,7 @@ import ts from 'typescript'; export class NodeValidator { private readonly sdkApiToPackageMap: Map = new Map([ - ['.sdkApiVersion', ['@ohos.deviceInfo.d.ts']], + ['sdkApiVersion', ['@ohos.deviceInfo.d.ts']], ]); private readonly compatibleSdkVersion: string; @@ -16,27 +16,23 @@ export class NodeValidator { /** * Checks whether a given node is valid for at least one condition. * @param node The node to check (e.g., a function call). - * @returns {boolean} True if the node is within a 'try' block. + * @returns {boolean} True if the node is protected by any validation mechanism. */ public isNodeHandled(node: ts.Node): boolean { return ( this.isNodeWrappedInTryCatch(node) || this.isNodeWrappedInUndefinedCheck(node) || - this.isNodeWrappedInSdkComparison(node) + this.isNodeWrappedInSdkComparison(node) ); } public isNodeWrappedInTryCatch(node: ts.Node): boolean { - let currentNode = node.parent; - while (currentNode) { - if (ts.isTryStatement(currentNode)) { - if (node.getStart() >= currentNode.tryBlock.getStart()) { - return true; - } + return this.findParentNode(node, (parent) => { + if (ts.isTryStatement(parent)) { + return node.getStart() >= parent.tryBlock.getStart(); } - currentNode = currentNode.parent; - } - return false; + return false; + }) !== null; } public isNodeWrappedInUndefinedCheck(node: ts.Node): boolean { @@ -45,34 +41,49 @@ export class NodeValidator { return false; } - let currentNode = node.parent; - while (currentNode) { - if (ts.isIfStatement(currentNode)) { - if (this.isUndefinedCheckHelper(currentNode.expression, targetName)) { - return true; - } + return this.findParentNode(node, (parent) => { + if (ts.isIfStatement(parent)) { + return this.isUndefinedCheckHelper(parent.expression, targetName); } - currentNode = currentNode.parent; - } - return false; + return false; + }) !== null; } - public isNodeWrappedInSdkComparison(node: ts.Node): boolean { - if (this.compatibleSdkVersion === '') { + if (this.compatibleSdkVersion === '' || !this.typeChecker) { return false; } + return this.findParentNode(node, (parent) => { + if (ts.isIfStatement(parent)) { + try { + return this.isSdkComparisonHelper(parent.expression); + } catch { + return false; + } + } + return false; + }) !== null; + } + + /** + * Finds the parent node that matches a given predicate. + * @param node The starting node to search from. + * @param predicate A function that checks if a parent node matches a condition. + * @returns {ts.Node | null} The first matching parent node or null if none found. + */ + private findParentNode( + node: ts.Node, + predicate: (parent: ts.Node) => boolean + ): ts.Node | null { let currentNode = node.parent; while (currentNode) { - if (ts.isIfStatement(currentNode)) { - if (this.isSdkComparisonHelper(currentNode.expression)) { - return true; - } + if (predicate(currentNode)) { + return currentNode; } currentNode = currentNode.parent; } - return false; + return null; } private getPrimaryNameFromNode(node: ts.Node): string | undefined { @@ -93,127 +104,116 @@ export class NodeValidator { return false; } - const allowedOperators = new Set([ + const isNotEqualOperator = [ ts.SyntaxKind.ExclamationEqualsEqualsToken, ts.SyntaxKind.ExclamationEqualsToken - ]); - if (!allowedOperators.has(expression.operatorToken.kind)) { + ].includes(expression.operatorToken.kind); + + if (!isNotEqualOperator) { return false; } - const isUndefinedNode = (node: ts.Node): boolean => - ts.isIdentifier(node) && node.text === 'undefined'; + const { left, right } = expression; + const isLeftUndefined = this.isUndefinedNode(left); + const isRightUndefined = this.isUndefinedNode(right); + const isLeftTarget = this.isTargetNode(left, name); + const isRightTarget = this.isTargetNode(right, name); - const isTargetNode = (node: ts.Node): boolean => { - const nodePrimaryName = this.getPrimaryNameFromNode(node); - return nodePrimaryName === name; - }; + return (isLeftTarget && isRightUndefined) || (isLeftUndefined && isRightTarget); + } - const { left, right } = expression; - const case1 = isTargetNode(left) && isUndefinedNode(right); - const case2 = isUndefinedNode(left) && isTargetNode(right); + private isUndefinedNode(node: ts.Node): boolean { + return ts.isIdentifier(node) && node.text === 'undefined'; + } - return case1 || case2; + private isTargetNode(node: ts.Node, name: string): boolean { + const nodePrimaryName = this.getPrimaryNameFromNode(node); + return nodePrimaryName === name; } private isSdkComparisonHelper(expression: ts.Expression): boolean { const expressionText = expression.getText(); - const matchedApiEntry = Array.from(this.sdkApiToPackageMap.entries()) - .find(([api]) => expressionText.includes(api)); - - if (!matchedApiEntry) { - return false; - } - const [matchedApi, validPackagePaths] = matchedApiEntry; + const matchedEntry = Array.from(this.sdkApiToPackageMap.entries()) + .find(([api]) => expressionText.includes(api)); - if (!this.typeChecker) { + if (!matchedEntry) { return false; } + const [matchedApi, validPackagePaths] = matchedEntry; const apiIdentifier = this.findApiIdentifier(expression, matchedApi); - if (!apiIdentifier) { - return false; - } - try { - return this.isValidSdkDeclaration(apiIdentifier, validPackagePaths); - } catch (err) { - return false; - } + return apiIdentifier + ? this.isValidSdkDeclaration(apiIdentifier, validPackagePaths) + : false; } private findApiIdentifier(expression: ts.Expression, api: string): ts.Identifier | undefined { - const apiParts = api.split('.'); - if (apiParts.length < 2) return undefined; - - const propertyName = apiParts[0]; - const finalProperty = apiParts[1]; - if (ts.isBinaryExpression(expression)) { - const leftId = this.extractApiIdentifierFromExpression(expression.left, finalProperty); - if (leftId) return leftId; - - const rightId = this.extractApiIdentifierFromExpression(expression.right, finalProperty); - if (rightId) return rightId; + return this.extractApiIdentifierFromExpression(expression.left, api) || + this.extractApiIdentifierFromExpression(expression.right, api); } - return this.extractApiIdentifierFromExpression(expression, finalProperty); + return this.extractApiIdentifierFromExpression(expression, api); } private extractApiIdentifierFromExpression( expression: ts.Expression, - finalProperty: string + targetProperty: string ): ts.Identifier | undefined { - if (!ts.isPropertyAccessExpression(expression)) { return undefined; } - if (expression.name.text !== finalProperty) { + if (expression.name.text !== targetProperty) { return undefined; } - let current: ts.Expression = expression.expression; + return this.getRootIdentifier(expression.expression); + } + + private getRootIdentifier(expression: ts.Expression): ts.Identifier | undefined { + let current: ts.Expression = expression; while (ts.isPropertyAccessExpression(current)) { current = current.expression; } - if (ts.isIdentifier(current)) { - return current; - } - - return undefined; + return ts.isIdentifier(current) ? current : undefined; } private isValidSdkDeclaration(identifier: ts.Identifier, validPackagePaths: string[]): boolean { - if (!this.typeChecker) return false; - - const symbol = this.typeChecker.getSymbolAtLocation(identifier); - if (!symbol) { + if (!this.typeChecker) { return false; } - const actualDeclarationFile = this.getActualDeclarationFile(symbol); - if (!actualDeclarationFile) { + const symbol = this.typeChecker.getSymbolAtLocation(identifier); + if (!symbol) { return false; } - return this.isValidSdkDeclarationPath(actualDeclarationFile, validPackagePaths); + const declarationFile = this.getActualDeclarationFile(symbol); + return declarationFile + ? this.isValidSdkDeclarationPath(declarationFile, validPackagePaths) + : false; } private getActualDeclarationFile(symbol: ts.Symbol): string | undefined { - const aliasedSymbol = this.typeChecker!.getAliasedSymbol(symbol); - const targetSymbol = aliasedSymbol !== symbol ? aliasedSymbol : symbol; + if (!this.typeChecker) { + return undefined; + } + + const targetSymbol = this.typeChecker.getAliasedSymbol(symbol); + const actualSymbol = targetSymbol !== symbol ? targetSymbol : symbol; - if (!targetSymbol.declarations || targetSymbol.declarations.length === 0) { + if (!actualSymbol.declarations?.length) { return undefined; } - const declarationFile = targetSymbol.declarations[0].getSourceFile().fileName; - const relativePath = declarationFile.replace(/^.*sdk[\\/].*[\\/](?=@)/, ''); - return relativePath; + const declarationFile = actualSymbol.declarations[0].getSourceFile().fileName; + + return declarationFile.replace(/^.*sdk[\\/].*[\\/](?=@)/, ''); } private isValidSdkDeclarationPath(filePath: string, validPackagePaths: string[]): boolean { -- Gitee From f2c2ab27ec710fe9f33b1e19c39042b5f2b1a26c Mon Sep 17 00:00:00 2001 From: bulutcan99 Date: Wed, 13 Aug 2025 20:53:42 +0300 Subject: [PATCH 6/6] refactor: add comments for section crossing --- .../fast_build/system_api/node_validator.ts | 125 +++++++++--------- 1 file changed, 59 insertions(+), 66 deletions(-) diff --git a/compiler/src/fast_build/system_api/node_validator.ts b/compiler/src/fast_build/system_api/node_validator.ts index 4069463d4..3fafa3abb 100644 --- a/compiler/src/fast_build/system_api/node_validator.ts +++ b/compiler/src/fast_build/system_api/node_validator.ts @@ -15,8 +15,6 @@ export class NodeValidator { /** * Checks whether a given node is valid for at least one condition. - * @param node The node to check (e.g., a function call). - * @returns {boolean} True if the node is protected by any validation mechanism. */ public isNodeHandled(node: ts.Node): boolean { return ( @@ -26,6 +24,9 @@ export class NodeValidator { ); } + // ─────────────────────────────────────────────────────────────── + // TRY-CATCH CHECK BLOCK + // ─────────────────────────────────────────────────────────────── public isNodeWrappedInTryCatch(node: ts.Node): boolean { return this.findParentNode(node, (parent) => { if (ts.isTryStatement(parent)) { @@ -35,6 +36,9 @@ export class NodeValidator { }) !== null; } + // ─────────────────────────────────────────────────────────────── + // UNDEFINED CHECK BLOCK + // ─────────────────────────────────────────────────────────────── public isNodeWrappedInUndefinedCheck(node: ts.Node): boolean { const targetName = this.getPrimaryNameFromNode(node); if (!targetName) { @@ -49,56 +53,6 @@ export class NodeValidator { }) !== null; } - public isNodeWrappedInSdkComparison(node: ts.Node): boolean { - if (this.compatibleSdkVersion === '' || !this.typeChecker) { - return false; - } - - return this.findParentNode(node, (parent) => { - if (ts.isIfStatement(parent)) { - try { - return this.isSdkComparisonHelper(parent.expression); - } catch { - return false; - } - } - return false; - }) !== null; - } - - /** - * Finds the parent node that matches a given predicate. - * @param node The starting node to search from. - * @param predicate A function that checks if a parent node matches a condition. - * @returns {ts.Node | null} The first matching parent node or null if none found. - */ - private findParentNode( - node: ts.Node, - predicate: (parent: ts.Node) => boolean - ): ts.Node | null { - let currentNode = node.parent; - while (currentNode) { - if (predicate(currentNode)) { - return currentNode; - } - currentNode = currentNode.parent; - } - return null; - } - - private getPrimaryNameFromNode(node: ts.Node): string | undefined { - if (ts.isIdentifier(node)) { - return node.text; - } - if (ts.isCallExpression(node)) { - return this.getPrimaryNameFromNode(node.expression); - } - if (ts.isPropertyAccessExpression(node)) { - return node.name.text; - } - return undefined; - } - private isUndefinedCheckHelper(expression: ts.Expression, name: string): boolean { if (!ts.isBinaryExpression(expression)) { return false; @@ -126,9 +80,24 @@ export class NodeValidator { return ts.isIdentifier(node) && node.text === 'undefined'; } - private isTargetNode(node: ts.Node, name: string): boolean { - const nodePrimaryName = this.getPrimaryNameFromNode(node); - return nodePrimaryName === name; + // ─────────────────────────────────────────────────────────────── + // SDK COMPARISON CHECK BLOCK + // ─────────────────────────────────────────────────────────────── + public isNodeWrappedInSdkComparison(node: ts.Node): boolean { + if (this.compatibleSdkVersion === '' || !this.typeChecker) { + return false; + } + + return this.findParentNode(node, (parent) => { + if (ts.isIfStatement(parent)) { + try { + return this.isSdkComparisonHelper(parent.expression); + } catch { + return false; + } + } + return false; + }) !== null; } private isSdkComparisonHelper(expression: ts.Expression): boolean { @@ -154,7 +123,6 @@ export class NodeValidator { return this.extractApiIdentifierFromExpression(expression.left, api) || this.extractApiIdentifierFromExpression(expression.right, api); } - return this.extractApiIdentifierFromExpression(expression, api); } @@ -165,21 +133,17 @@ export class NodeValidator { if (!ts.isPropertyAccessExpression(expression)) { return undefined; } - if (expression.name.text !== targetProperty) { return undefined; } - return this.getRootIdentifier(expression.expression); } private getRootIdentifier(expression: ts.Expression): ts.Identifier | undefined { let current: ts.Expression = expression; - while (ts.isPropertyAccessExpression(current)) { current = current.expression; } - return ts.isIdentifier(current) ? current : undefined; } @@ -187,12 +151,10 @@ export class NodeValidator { if (!this.typeChecker) { return false; } - const symbol = this.typeChecker.getSymbolAtLocation(identifier); if (!symbol) { return false; } - const declarationFile = this.getActualDeclarationFile(symbol); return declarationFile ? this.isValidSdkDeclarationPath(declarationFile, validPackagePaths) @@ -203,16 +165,12 @@ export class NodeValidator { if (!this.typeChecker) { return undefined; } - const targetSymbol = this.typeChecker.getAliasedSymbol(symbol); const actualSymbol = targetSymbol !== symbol ? targetSymbol : symbol; - if (!actualSymbol.declarations?.length) { return undefined; } - const declarationFile = actualSymbol.declarations[0].getSourceFile().fileName; - return declarationFile.replace(/^.*sdk[\\/].*[\\/](?=@)/, ''); } @@ -226,4 +184,39 @@ export class NodeValidator { private normalizePath(path: string): string { return path.replace(/\\/g, '/').toLowerCase(); } + + // ─────────────────────────────────────────────────────────────── + // COMMON HELPERS (USED ACROSS MULTIPLE BLOCKS) + // ─────────────────────────────────────────────────────────────── + private findParentNode( + node: ts.Node, + predicate: (parent: ts.Node) => boolean + ): ts.Node | null { + let currentNode = node.parent; + while (currentNode) { + if (predicate(currentNode)) { + return currentNode; + } + currentNode = currentNode.parent; + } + return null; + } + + private getPrimaryNameFromNode(node: ts.Node): string | undefined { + if (ts.isIdentifier(node)) { + return node.text; + } + if (ts.isCallExpression(node)) { + return this.getPrimaryNameFromNode(node.expression); + } + if (ts.isPropertyAccessExpression(node)) { + return node.name.text; + } + return undefined; + } + + private isTargetNode(node: ts.Node, name: string): boolean { + const nodePrimaryName = this.getPrimaryNameFromNode(node); + return nodePrimaryName === name; + } } -- Gitee