JSDoc整改工具功能优化

Signed-off-by: yangbo_404 <yangbo198@huawei.com>
Change-Id: Iaefa2270ba31a978e49c2cbed459dd7416bdda4b
This commit is contained in:
yangbo_404 2023-03-13 02:42:21 +00:00 committed by wangtao
parent 89b3c878c7
commit d103ef4a5b
31 changed files with 738 additions and 222 deletions

View File

@ -6,7 +6,6 @@
"default",
"deprecated",
"enum",
"errorcode",
"example",
"extends",
"famodelonly",
@ -41,18 +40,12 @@
"borrows",
"class",
"classdesc",
"constant",
"constructs",
"copyright",
"default",
"deprecated",
"enum",
"event",
"example",
"exports",
"external",
"file",
"fires",
"function",
"generator",
"global",
@ -62,7 +55,6 @@
"inheritdoc",
"inner",
"instance",
"interface",
"lends",
"license",
"listens",
@ -72,26 +64,17 @@
"mixin",
"modifies",
"module",
"namespace",
"package",
"param",
"private",
"property",
"protected",
"public",
"readonly",
"requires",
"returns",
"see",
"since",
"static",
"summary",
"this",
"todo",
"throws",
"tutorial",
"type",
"typedef",
"variation",
"version",
"yields",

View File

@ -1256,6 +1256,15 @@
"provisionEnable": true,
"distributedSceneEnable": false
},
{
"name": "ohos.permission.ENTERPRISE_SET_ACCOUNT_POLICY",
"grantMode": "system_grant",
"availableLevel": "system_basic",
"since": 10,
"deprecated": "",
"provisionEnable": true,
"distributedSceneEnable": false
},
{
"name": "ohos.permission.NFC_TAG",
"grantMode": "system_grant",
@ -1574,6 +1583,24 @@
"provisionEnable": true,
"distributedSceneEnable": false
},
{
"name": "ohos.permission.READ_APP_PUSH_DATA",
"grantMode": "system_grant",
"availableLevel": "system_basic",
"since": 10,
"deprecated": "",
"provisionEnable": false,
"distributedSceneEnable": false
},
{
"name": "ohos.permission.WRITE_APP_PUSH_DATA",
"grantMode": "system_grant",
"availableLevel": "system_basic",
"since": 10,
"deprecated": "",
"provisionEnable": false,
"distributedSceneEnable": false
},
{
"name": "ohos.permission.MANAGE_AUDIO_CONFIG",
"grantMode": "system_grant",
@ -1669,7 +1696,34 @@
"deprecated": "",
"provisionEnable": true,
"distributedSceneEnable": false
},
{
"name": "ohos.permission.PRINT",
"grantMode": "system_grant",
"since": 10,
"deprecated": "",
"availableLevel": "normal",
"provisionEnable": true,
"distributedSceneEnable": false
},
{
"name": "ohos.permission.MANAGE_PRINT_JOB",
"grantMode": "system_grant",
"since": 10,
"deprecated": "",
"availableLevel": "system_basic",
"provisionEnable": true,
"distributedSceneEnable": false
},
{
"name": "ohos.permission.CHANGE_OVERLAY_ENABLED_STATE",
"grantMode": "system_grant",
"availableLevel": "system_basic",
"since": 10,
"deprecated": "",
"provisionEnable": true,
"distributedSceneEnable": false
}
]
}
}
}

View File

@ -13,7 +13,79 @@
* limitations under the License.
*/
const checkLegality = require('./src/check_legality');
exports.checkJSDoc = function (node, sourceFile) {
return checkLegality.checkJsDocOfCurrentNode(node, sourceFile, undefined);
};
const fs = require('fs');
const path = require('path');
const urlPrefix = 'https://gitee.com/openharmony/utils_system_resources/raw/';
const urlSuffix = '/systemres/main/config.json';
exports.checkJSDoc = function (node, sourceFile, fileName) {
const checkLegality = require('./src/check_legality');
return checkLegality.checkJsDocOfCurrentNode(node, sourceFile, undefined, fileName);
};
exports.initEnv = function (version) {
const { checkOption } = require('./src/utils');
return new Promise((resolve, reject) => {
const permissionName = getPermissionName(version);
const permissionConfig = getLocalPermissionConfig(permissionName);
if (permissionConfig) {
checkOption.permissionContent = permissionConfig;
resolve();
return;
}
const workingBranch = version ? version : 'mas' + 'ter';
const url = `${urlPrefix}${workingBranch}${urlSuffix}`;
updatePermissionConfig(url, (content) => {
if (content) {
checkOption.permissionContent = content;
savePermissionConfig(content, permissionName);
}
resolve();
});
});
}
function updatePermissionConfig(url, callback) {
let requestText = undefined;
const https = require('https');
const request = https.get(url, { timeout: 2000 }, (res) => {
res.on('data', (chunk) => {
if (typeof chunk === 'string') {
requestText = chunk;
} else {
const dataStr = new TextDecoder().decode(chunk);
requestText = requestText ? (requestText + dataStr) : dataStr;
}
});
}).on('error', () => {
console.warn('use the default permission list.');
}).on('close', () => {
callback(requestText);
}).on('timeout', () => {
request.destroy();
});
}
function getLocalPermissionConfig(name) {
const localPermissionFile = path.resolve(__dirname, name);
if (fs.existsSync(localPermissionFile)) {
const content = fs.readFileSync(localPermissionFile);
try {
JSON.parse(content);
return content;
} catch (err) {
console.warn(`parse error ${localPermissionFile}`);
}
}
return undefined;
}
function savePermissionConfig(content, name) {
const localPermissionFile = path.resolve(__dirname, name);
fs.writeFileSync(localPermissionFile, content);
console.log(`update permission configuration to ${localPermissionFile}`);
}
function getPermissionName(version) {
return `permissions_${version}.config.json`;
}

View File

@ -15,13 +15,13 @@
const path = require('path');
const fs = require('fs');
const ts = require(path.resolve(__dirname, '../node_modules/typescript'));
const { checkAPIDecorators } = require('./check_decorator');
const { checkSpelling } = require('./check_spelling');
const { checkPermission } = require('./check_permission');
const { checkSyscap } = require('./check_syscap');
const { checkDeprecated } = require('./check_deprecated');
const { hasAPINote, ApiCheckResult } = require('./utils');
const { hasAPINote, ApiCheckResult, requireTypescriptModule } = require('./utils');
const ts = requireTypescriptModule();
let result = require('../check_result.json');
function checkAPICodeStyle(url) {

View File

@ -0,0 +1,3 @@
{
"isBundle": false
}

View File

@ -14,10 +14,10 @@
*/
const path = require('path');
const ts = require(path.resolve(__dirname, '../node_modules/typescript'));
const { ErrorType, ErrorLevel } = require('./utils');
const { ErrorType, ErrorLevel, requireTypescriptModule } = require('./utils');
const rules = require('../code_style_rule.json');
const { addAPICheckErrorLogs } = require('./compile_info');
const ts = requireTypescriptModule();
function checkAPINameOfHump(node, sourcefile, fileName) {
let errorInfo = '';

View File

@ -12,11 +12,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const ts = require(path.resolve(__dirname, '../../node_modules/typescript'));
const rules = require('../../code_style_rule.json');
const { ErrorLevel, FileType, ErrorType, commentNodeWhiteList } = require('../../src/utils');
const { ErrorLevel, FileType, ErrorType, commentNodeWhiteList, requireTypescriptModule } = require('../../src/utils');
const { addAPICheckErrorLogs } = require('../compile_info');
const { getPermissionBank } = require('../check_permission');
const ts = requireTypescriptModule();
function checkExtendsValue(tag, node, sourcefile, fileName, index) {
@ -29,15 +29,9 @@ function checkExtendsValue(tag, node, sourcefile, fileName, index) {
// 获取api中的extends信息校验标签合法性及值规范
if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) {
const apiValue = node.heritageClauses ? node.heritageClauses[0].types[0].expression.escapedText : '';
if (apiValue.length === 0) {
if (tagValue !== apiValue) {
extendsResult.checkResult = false,
extendsResult.errorInfo = 'should delete @extends; ';
addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.WRONG_ORDER, extendsResult.errorInfo, FileType.JSDOC,
ErrorLevel.LOW);
} else if (tagValue !== apiValue) {
extendsResult.checkResult = false,
extendsResult.errorInfo = ` '@${tagValue}' should change to '${apiValue}'; `;
extendsResult.errorInfo = 'extends标签值错误, 请检查标签值是否与继承类名保持一致.';
addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.WRONG_ORDER, extendsResult.errorInfo, FileType.JSDOC,
ErrorLevel.LOW);
}
@ -58,7 +52,7 @@ function checkEnumValue(tag, node, sourcefile, fileName, index) {
// 获取api中的enum信息校验标签合法性及值规范
if (tagProblems > 0 || enumValues.indexOf(tagValue) === -1) {
enumResult.checkResult = false;
enumResult.errorInfo = '@enum value is wrong; ';
enumResult.errorInfo = 'enum标签类型错误, 请检查标签类型是否为string或number.';
addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.WRONG_ORDER, enumResult.errorInfo, FileType.JSDOC,
ErrorLevel.LOW);
}
@ -71,10 +65,11 @@ function checkSinceValue(tag, node, sourcefile, fileName, index) {
checkResult: true,
errorInfo: '',
};
const tagValue = parseInt(tag.name);
if (isNaN(tagValue)) {
const tagValue = tag.name;
const checkNumber = /^\d+$/.test(tagValue);
if (!checkNumber && commentNodeWhiteList.includes(node.kind)) {
sinceResult.checkResult = false;
sinceResult.errorInfo = `@since value '${tag.name}' is wrong; `;
sinceResult.errorInfo = 'since标签值错误, 请检查标签值是否为数值.';
addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.WRONG_ORDER, sinceResult.errorInfo, FileType.JSDOC,
ErrorLevel.LOW);
}
@ -93,12 +88,12 @@ function checkReturnsValue(tag, node, sourcefile, fileName, index) {
const apiReturnsValue = node.type?.getText();
if (voidArr.indexOf(apiReturnsValue) !== -1 || apiReturnsValue === undefined) {
returnsResult.checkResult = false;
returnsResult.errorInfo = 'should delete @returns; ';
returnsResult.errorInfo = 'returns标签使用错误, 返回类型为void时不应该使用returns标签.';
addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.WRONG_ORDER, returnsResult.errorInfo, FileType.JSDOC,
ErrorLevel.LOW);
} else if (tagValue !== apiReturnsValue) {
returnsResult.checkResult = false;
returnsResult.errorInfo = `@returns value '${tagValue}' is wrong; `;
returnsResult.errorInfo = 'returns标签类型错误, 请检查标签类型是否与返回类型一致.';
addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.WRONG_ORDER, returnsResult.errorInfo, FileType.JSDOC,
ErrorLevel.LOW);
}
@ -119,22 +114,23 @@ function checkParamValue(tag, node, sourcefile, fileName, index) {
if (apiParamInfos[index]) {
const apiName = apiParamInfos[index].name.escapedText;
const apiType = apiParamInfos[index].type?.getText();
let errorInfo = '';
if (apiType !== tagTypeValue) {
paramResult.checkResult = false;
errorInfo += `第[${index + 1}]个param标签类型错误, 请检查是否与第[${index + 1}]个参数类型保持一致.`;
}
if (apiName !== tagNameValue) {
paramResult.checkResult = false;
paramResult.errorInfo = `@param name '${tagNameValue}' is wrong; `;
addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.WRONG_ORDER, paramResult.errorInfo, FileType.JSDOC,
ErrorLevel.LOW);
} else if (apiType !== tagTypeValue) {
paramResult.checkResult = false;
paramResult.errorInfo = `@param type '${tagTypeValue}' is wrong; `;
addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.WRONG_ORDER, paramResult.errorInfo, FileType.JSDOC,
if (errorInfo !== '') {
errorInfo += '\n';
}
errorInfo += `第[${index + 1}]个param标签值错误, 请检查是否与第[${index + 1}]个参数名保持一致.`;
}
if (!paramResult.checkResult) {
paramResult.errorInfo = errorInfo;
addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.WRONG_ORDER, errorInfo, FileType.JSDOC,
ErrorLevel.LOW);
}
} else {
paramResult.checkResult = false;
paramResult.errorInfo = '@param counts is wrong; ';
addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.WRONG_ORDER, paramResult.errorInfo, FileType.JSDOC,
ErrorLevel.LOW);
}
}
return paramResult;
@ -148,32 +144,109 @@ function checkThrowsValue(tag, node, sourcefile, fileName, index) {
};
const tagNameValue = tag.name;
const tagTypeValue = tag.type;
let errorInfo = '';
if (tagTypeValue !== 'BusinessError') {
throwsResult.checkResult = false;
throwsResult.errorInfo = `@throws type value '${tagTypeValue}' is wrong; `;
addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.WRONG_ORDER, throwsResult.errorInfo, FileType.JSDOC,
ErrorLevel.LOW);
} else if (isNaN(tagNameValue)) {
errorInfo += `第[${index + 1}]个throws标签类型错误, 请填写BusinessError.`;
}
if (isNaN(tagNameValue)) {
if (errorInfo !== '') {
errorInfo += '\n';
}
throwsResult.checkResult = false;
throwsResult.errorInfo = `@throws name value '${tag.name}' is wrong; `;
addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.WRONG_ORDER, throwsResult.errorInfo, FileType.JSDOC,
errorInfo += `第[${index + 1}]个throws标签类型错误, 请检查标签值是否为数值.`;
}
if (!throwsResult.checkResult) {
throwsResult.errorInfo = errorInfo;
addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.WRONG_ORDER, errorInfo, FileType.JSDOC,
ErrorLevel.LOW);
}
return throwsResult;
}
exports.checkThrowsValue = checkThrowsValue;
// #校验功能待补全
/**
* 判断是否为arkui的api文件
*/
function isArkUIApiFile(fileName) {
if (fileName.indexOf("component\\ets\\") >= 0 || fileName.indexOf("component/ets/") >= 0) {
return true;
}
return false;
}
/**
*
* 1.引用不同文件的api接口
* xxx.xxx#xxx
*
* 2.引用不同文件的模块接口
* xxx.xxx
*
* 3.引用不同文件的api事件接口
* xxx.xxx#event:xxx
*/
function checkModule(moduleValue) {
return /^[A-Za-z]+\b(\.[A-Za-z]+\b)*$/.test(moduleValue) ||
/^[A-Za-z]+\b(\.[A-Za-z]+\b)*\#[A-Za-z]+\b$/.test(moduleValue) ||
/^[A-Za-z]+\b(\.[A-Za-z]+\b)*\#event:[A-Za-z]+\b$/.test(moduleValue);
}
function splitUseinsteadValue(useinsteadValue) {
if (!useinsteadValue || useinsteadValue === '') {
return undefined;
}
const splitResult = {
checkResult: true,
errorInfo: ''
}
// 拆分字符串
const splitArray = useinsteadValue.split(/\//g);
if (splitArray.length === 1) {
// 同一文件
if (!checkModule(splitArray[0])) {
splitResult.checkResult = false;
}
} else if (splitArray.length === 2) {
// 不同文件
const fileNameArray = splitArray[0].split('.');
if (fileNameArray.length === 1) {
// arkui
if (!/^[A-Za-z]+\b$/.test(fileNameArray[0]) || !checkModule(splitArray[1])) {
splitResult.checkResult = false;
}
} else {
// 非arkui
let checkFileName = true;
for (let i = 0; i < fileNameArray.length; i++) {
if (fileNameArray[0] !== 'ohos' || !/^[A-Za-z]+\b$/.test(fileNameArray[i])) {
checkFileName = false;
}
}
if (!checkFileName || !checkModule(splitArray[1])) {
splitResult.checkResult = false;
}
}
} else {
// 格式错误
splitResult.checkResult = false;
}
if (!splitResult.checkResult) {
splitResult.errorInfo = 'useinstead标签值错误, 请检查使用方法.';
}
return splitResult;
}
// 精确校验功能待补全
function checkUseinsteadValue(tag, node, sourcefile, fileName, index) {
const tagNameValue = tag.name;
let useinsteadResult = {
checkResult: true,
errorInfo: '',
};
const tagNameValue = tag.name;
if (tagNameValue.indexOf('ohos') === -1 || tagNameValue.indexOf('/') === -1) {
useinsteadResult.checkResult = false;
useinsteadResult.errorInfo = `@useinstead value '${tagNameValue}' is wrong; `;
const result = splitUseinsteadValue(tagNameValue, fileName);
if (result && !result.checkResult) {
useinsteadResult = result;
addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.WRONG_ORDER, useinsteadResult.errorInfo, FileType.JSDOC,
ErrorLevel.LOW);
}
@ -191,7 +264,7 @@ function checkTypeValue(tag, node, sourcefile, fileName, index) {
const apiTypeValue = node.type?.getText();
if (apiTypeValue !== tagTypeValue) {
typeResult.checkResult = false;
typeResult.errorInfo = `@type value '${tagTypeValue}' is wrong; `;
typeResult.errorInfo = 'type标签类型错误, 请检查类型是否与属性类型一致.';
addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.WRONG_ORDER, typeResult.errorInfo, FileType.JSDOC,
ErrorLevel.LOW);
}
@ -207,7 +280,7 @@ function checkDefaultValue(tag, node, sourcefile, fileName, index) {
};
if (commentNodeWhiteList.includes(node.kind) && tag.name.length === 0 && tag.type.length === 0) {
defaultResult.checkResult = false;
defaultResult.errorInfo = 'should add @default value; ';
defaultResult.errorInfo = 'default标签值错误, 请补充默认值.';
addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.WRONG_ORDER, defaultResult.errorInfo, FileType.JSDOC,
ErrorLevel.LOW);
}
@ -226,18 +299,9 @@ function checkPermissionTag(tag, node, sourcefile, fileName, index) {
const tagValue = tag.name + tag.description;
const permissionArr = tagValue.replace(/ /g, '').replace(/(or|and|\(|\))/g, '$').split('$');
permissionArr.forEach(permissionStr => {
if (permissionStr !== '') {
if (!permissionRuleSet.has(permissionStr) && permissionStr !== 'N/A') {
hasPermissionError = true;
if (errorInfo !== '') {
errorInfo += `,${permissionStr}`;
} else {
errorInfo += permissionStr;
}
}
} else {
if ((permissionStr !== '' && !permissionRuleSet.has(permissionStr) && permissionStr !== 'N/A') || permissionStr === '') {
hasPermissionError = true;
errorInfo = 'permission value is null';
errorInfo = 'permission标签值书写错误, 请检查权限字段是否已配置或者更新配置文件.';
}
});
if (hasPermissionError) {
@ -257,9 +321,10 @@ function checkDeprecatedTag(tag, node, sourcefile, fileName, index) {
};
const tagValue1 = tag.name;
const tagValue2 = tag.description;
if (tagValue1 !== 'since' || isNaN(parseFloat(tagValue2))) {
const checkNumber = /^\d+$/.test(tagValue2);
if ((tagValue1 !== 'since' || !checkNumber) && commentNodeWhiteList.includes(node.kind)) {
deprecatedResult.checkResult = false;
deprecatedResult.errorInfo = '@deprecated value is wrong';
deprecatedResult.errorInfo = 'deprecated标签值错误, 请检查使用方法.';
addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.UNKNOW_PERMISSION, deprecatedResult.errorInfo,
FileType.API, ErrorLevel.LOW);
}
@ -283,7 +348,7 @@ function checkSyscapTag(tag, node, sourcefile, fileName, index) {
}
if (!syscapRuleSet.has(tagValue)) {
syscapResult.checkResult = false;
syscapResult.errorInfo = '@syscap value is wrong';
syscapResult.errorInfo = 'syscap标签值错误, 请检查syscap字段是否已配置.';
addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.UNKNOW_PERMISSION, syscapResult.errorInfo,
FileType.API, ErrorLevel.LOW);
}
@ -301,7 +366,7 @@ function checkNamespaceTag(tag, node, sourcefile, fileName) {
let apiValue = node.name?.escapedText;
if (apiValue !== undefined && tagValue !== apiValue) {
namespaceResult.checkResult = false;
namespaceResult.errorInfo = '@namespace value is wrong';
namespaceResult.errorInfo = 'namespace标签值错误, 请检查是否与namespace名称保持一致.';
addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.UNKNOW_PERMISSION, namespaceResult.errorInfo,
FileType.API, ErrorLevel.LOW);
}
@ -310,6 +375,29 @@ function checkNamespaceTag(tag, node, sourcefile, fileName) {
}
exports.checkNamespaceTag = checkNamespaceTag;
function checkInterfaceTypedefTag(tag, node, sourcefile, fileName) {
let interfaceResult = {
checkResult: true,
errorInfo: '',
};
const tagValue = tag.name;
if (commentNodeWhiteList.includes(node.kind)) {
let apiValue = node.name?.escapedText;
if (apiValue !== undefined && tagValue !== apiValue) {
interfaceResult.checkResult = false;
if (tag.tag === 'interface') {
interfaceResult.errorInfo = 'interface标签值错误, 请检查是否与interface名称保持一致.';
} else if (tag.tag === 'typedef') {
interfaceResult.errorInfo = 'typedef标签值错误, 请检查是否与interface名称保持一致.';
}
addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.UNKNOW_PERMISSION, interfaceResult.errorInfo,
FileType.API, ErrorLevel.LOW);
}
}
return interfaceResult;
}
exports.checkInterfaceTypedefTag = checkInterfaceTypedefTag;
const JsDocValueChecker = {
'extends': checkExtendsValue,
'enum': checkEnumValue,
@ -323,6 +411,8 @@ const JsDocValueChecker = {
'permission': checkPermissionTag,
'deprecated': checkDeprecatedTag,
'syscap': checkSyscapTag,
'namespace': checkNamespaceTag
'namespace': checkNamespaceTag,
'interface': checkInterfaceTypedefTag,
'typedef': checkInterfaceTypedefTag
};
exports.JsDocValueChecker = JsDocValueChecker;

View File

@ -13,15 +13,9 @@
* limitations under the License.
*/
const parse = require('comment-parser');
const { getAPINote, ErrorLevel, FileType, ErrorType } = require('../utils');
const { getAPINote, ErrorLevel, FileType, ErrorType, tagsArrayOfOrder } = require('../utils');
const { addAPICheckErrorLogs } = require('../compile_info');
const labelOrderArray = [
'namespace', 'extends', 'typedef', 'interface', 'permission', 'enum', 'constant', 'type', 'param', 'default',
'returns', 'readonly', 'throws', 'static', 'fires', 'syscap', 'systemapi', 'famodelonly', 'FAModelOnly',
'stagemodelonly', 'StageModelOnly', 'crossplatform', 'since', 'deprecated', 'useinstead', 'form', 'example'
];
/**
* 判断标签排列是否为升序
*/
@ -30,8 +24,8 @@ function isAscendingOrder(tags) {
tags.forEach((tag, index) => {
if (index + 1 < tags.length) {
// 获取前后两个tag下标
const firstIndex = labelOrderArray.indexOf(tag.tag);
const secondIndex = labelOrderArray.indexOf(tags[index + 1].tag);
const firstIndex = tagsArrayOfOrder.indexOf(tag.tag);
const secondIndex = tagsArrayOfOrder.indexOf(tags[index + 1].tag);
// 非自定义标签在前或数组降序时报错
if ((firstIndex === -1 && secondIndex !== -1) || firstIndex > secondIndex) {

View File

@ -15,9 +15,9 @@
const path = require('path');
const fs = require('fs');
const { parseJsDoc, commentNodeWhiteList } = require('./utils');
const ts = require(path.resolve(__dirname, "../node_modules/typescript"));
const { parseJsDoc, commentNodeWhiteList, requireTypescriptModule, tagsArrayOfOrder } = require('./utils');
const { checkApiOrder } = require('./check_jsdoc_value/chek_order');
const ts = requireTypescriptModule();
// 标签合法性校验
function checkJsDocLegality(node, sourcefile, checkInfoMap) {
@ -95,7 +95,7 @@ function checkJsDocLegality(node, sourcefile, checkInfoMap) {
}
);
// typedef/interface
legalityCheck(node, sourcefile, [ts.SyntaxKind.InterfaceDeclaration], ['interface'], true, checkInfoMap,
legalityCheck(node, sourcefile, [ts.SyntaxKind.InterfaceDeclaration], ['interface', 'typedef'], true, checkInfoMap,
(currentNode, checkResult) => {
return true;
}
@ -154,8 +154,6 @@ function legalityCheck(node, sourcefile, legalKinds, tagsName, isRequire, checkI
} else if (tag.tag === 'deprecated') {
useinsteadResultObj.hasDeprecated = true;
}
} if (tagName === 'interface' && (tag.tag === tagName || tag.tag === 'typedef')) {
checkResult = true;
} else if (tag.tag === tagName) {
checkResult = true;
}
@ -179,9 +177,14 @@ function legalityCheck(node, sourcefile, legalKinds, tagsName, isRequire, checkI
(tagName === 'param' && paramTagNum > parameterNum)) && extraCheckCallback(node, checkResult)) {
// 报错
// console.log(`${sourcefile.fileName}, ${node.getText()} should not has @${tagName}`);
let errorInfo = `不允许使用[${tagName}]标签, 请检查标签使用方法.`;
if (tagName === 'param') {
errorInfo = `第[${parameterNum + 1}]个[${tagName}]标签多余, 请检查是否应该删除标签.`;
}
checkInfoMap[index].illegalTags.push({
checkResult: false,
errorInfo: `api第[${index + 1}]段JSDoc不允许使用[${tagName}]标签, 请检查标签使用方法`,
errorInfo: errorInfo,
index: index
});
}
@ -190,30 +193,65 @@ function legalityCheck(node, sourcefile, legalKinds, tagsName, isRequire, checkI
return checkInfoMap;
}
function checkJsDocOfCurrentNode(node, sourcefile, permissionConfigPath) {
// 标签重复性检查
function checkTagsQuantity(comment, index) {
const multipleTags = ['throws', 'param']
const tagCountObj = {};
const checkResult = [];
comment.tags.forEach(tag => {
if (!tagCountObj[tag.tag]) {
tagCountObj[tag.tag] = 0;
}
tagCountObj[tag.tag] = tagCountObj[tag.tag] + 1;
});
for (const tagName in tagCountObj) {
if (tagCountObj[tagName] > 1 && multipleTags.indexOf(tagName) < 0) {
checkResult.push({
checkResult: false,
errorInfo: `[${tagName}]标签不允许重复使用, 请删除多余标签.`,
index: index
});
}
}
// interface/typedef互斥校验
if (tagCountObj['interface'] > 0 & tagCountObj['typedef'] > 0) {
checkResult.push({
checkResult: false,
errorInfo: 'interface标签与typedef标签不允许同时使用, 请确认接口类型.',
index: index
});
}
return checkResult;
}
function checkJsDocOfCurrentNode(node, sourcefile, permissionConfigPath, fileName) {
let { permissionFile } = require('./utils');
if (permissionConfigPath && fs.existsSync(permissionConfigPath)) {
permissionFile = permissionConfigPath;
}
const checkInfoMap = checkJsDocLegality(node, sourcefile, {});
const checkInfoArray = [];
const checkOrderResult = checkApiOrder(node, sourcefile, sourcefile.fileName);
const checkOrderResult = checkApiOrder(node, sourcefile, fileName);
checkOrderResult.forEach((result, index) => {
checkInfoMap[index.toString()].orderResult = result;
});
const comments = parseJsDoc(node);
comments.forEach((comment, index) => {
const errorLogs = [];
let errorLogs = [];
let paramIndex = 0;
let throwsIndex = 0;
// 值检验
comment.tags.forEach(tag => {
const { JsDocValueChecker } = require('./check_jsdoc_value/check_rest_value');
const checker = JsDocValueChecker[tag.tag];
if (checker) {
let valueCheckResult;
if (tag.tag === 'param') {
valueCheckResult = checker(tag, node, sourcefile, sourcefile.fileName, paramIndex);
valueCheckResult = checker(tag, node, sourcefile, fileName, paramIndex++);
} else if (tag.tag === 'throws') {
valueCheckResult = checker(tag, node, sourcefile, fileName, throwsIndex++);
} else {
valueCheckResult = checker(tag, node, sourcefile, sourcefile.fileName);
valueCheckResult = checker(tag, node, sourcefile, fileName);
}
if (!valueCheckResult.checkResult) {
valueCheckResult.index = index;
@ -222,6 +260,9 @@ function checkJsDocOfCurrentNode(node, sourcefile, permissionConfigPath) {
}
}
});
// 标签数量校验
const quantityCheckResult = checkTagsQuantity(comment, index);
errorLogs = errorLogs.concat(quantityCheckResult);
checkInfoMap[index.toString()].illegalTags = checkInfoMap[index.toString()].illegalTags.concat(errorLogs);
});
for (const key in checkInfoMap) {

View File

@ -21,6 +21,13 @@ const { addAPICheckErrorLogs } = require('./compile_info');
const permissionCheckWhitelist = new Set(['@ohos.wifi.d.ts', '@ohos.wifiManager.d.ts']);
/**
* 门禁环境优先使用systemPermissionFile
* 本地环境从指定分支上下载
* 下载失败则使用默认配置
*
* @returns Set<string>
*/
function getPermissionBank() {
const permissionTags = ['ohos.permission.HEALTH_DATA', 'ohos.permission.HEART_RATE', 'ohos.permission.ACCELERATION'];
let permissionFileContent;

View File

@ -15,8 +15,7 @@
const fs = require('fs');
const path = require('path');
const ts = require(path.resolve(__dirname, '../node_modules/typescript'));
const { hasAPINote, getAPINote, overwriteIndexOf, ErrorType, ErrorLevel, FileType } = require('./utils');
const { hasAPINote, getAPINote, overwriteIndexOf, ErrorType, ErrorLevel, FileType, requireTypescriptModule } = require('./utils');
const { addAPICheckErrorLogs } = require('./compile_info');
const rules = require('../code_style_rule.json');
const dictionariesContent = fs.readFileSync(path.resolve(__dirname, '../plugin/dictionaries.txt'), 'utf-8');
@ -25,7 +24,8 @@ const dictionariesSupplementaryContent = fs.readFileSync(path.resolve(__dirname,
'../plugin/dictionaries_supplementary.txt'), 'utf-8');
const dictionariesSupplementaryArr = dictionariesSupplementaryContent.split(/[(\r\n)\r\n]+/g);
const dictionariesSet = new Set([...dictionariesArr, ...dictionariesSupplementaryArr, ...rules.decorators.customDoc,
...rules.decorators.jsDoc]);
...rules.decorators.jsDoc]);
const ts = requireTypescriptModule();
function checkSpelling(node, sourcefile, fileName) {
if (ts.isIdentifier(node) && node.escapedText) {

View File

@ -16,7 +16,23 @@ const fs = require('fs');
const path = require('path');
const ExcelJS = require('exceljs');
const cm = require('comment-parser');
const ts = require(path.resolve(__dirname, '../node_modules/typescript'));
function requireTypescriptModule() {
const buildOption = require('./build.json');
if (buildOption.isBundle) {
return require('typescript');
}
const tsPathArray = [
path.resolve(__dirname, "../node_modules/typescript"),
path.resolve(__dirname, "../../node_modules/typescript")
];
if (fs.existsSync(tsPathArray[0])) {
return require(tsPathArray[0]);
} else if (fs.existsSync(tsPathArray[1])) {
return require(tsPathArray[1]);
}
}
exports.requireTypescriptModule = requireTypescriptModule;
const ts = requireTypescriptModule();
const commentNodeWhiteList = [
ts.SyntaxKind.PropertySignature, ts.SyntaxKind.CallSignature, ts.SyntaxKind.MethodSignature,
@ -28,6 +44,13 @@ const commentNodeWhiteList = [
];
exports.commentNodeWhiteList = commentNodeWhiteList;
const tagsArrayOfOrder = [
'namespace', 'extends', 'typedef', 'interface', 'permission', 'enum', 'constant', 'type', 'param', 'default',
'returns', 'readonly', 'throws', 'static', 'fires', 'syscap', 'systemapi', 'famodelonly', 'FAModelOnly',
'stagemodelonly', 'StageModelOnly', 'crossplatform', 'since', 'deprecated', 'useinstead', 'test', 'form', 'example'
];
exports.tagsArrayOfOrder = tagsArrayOfOrder;
function getAPINote(node) {
const apiLength = node.getText().length;
const apiFullLength = node.getFullText().length;

View File

@ -51,7 +51,3 @@ Options:
在上述命令成功执行完后,会同时生成一个 .xlsx 报告。可根据报告提示,修改错误。
报告出现在 -i 输入的文件/文件夹的同级目录,命名方式为 文件(夹)名_时间戳.xlsx
## 约束
Node.js version 15.0.0 及以上

View File

@ -0,0 +1,26 @@
/*
* Copyright (c) 2023 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.
*/
/** 修改 build.json, 同一份代码编译不同版本 */
module.exports = function (content, map, meta) {
const buildConfig = JSON.parse(content);
if (buildConfig.hasOwnProperty('isBundle') && buildConfig.isBundle === false) {
if (process.env.npm_config_bundle) {
buildConfig.isBundle = process.env.npm_config_bundle;
return JSON.stringify(buildConfig);
}
}
return content;
};

View File

@ -4,8 +4,11 @@
"description": "",
"main": "",
"scripts": {
"build": "webpack --mode=production",
"format": "ts-node src/main.ts -i ../../api/ -o result/"
"testAll": "npm install && mocha --config test/mocha/.mocharc.jsonc",
"testOnly": "nyc mocha --config test/mocha/.mocharc.jsonc --grep",
"pack": "webpack --mode=production",
"build": "npm run pack --bundle",
"formatSDK": "ts-node src/main.ts -i ../../api/ -o formatedSDK/"
},
"author": "",
"license": "ISC",
@ -30,6 +33,7 @@
"json-loader": "^0.5.7",
"mocha": "^7.1.1",
"mochawesome": "^7.1.3",
"nyc": "^15.1.0",
"terser-webpack-plugin": "^5.3.1",
"ts-loader": "^9.2.8",
"ts-node": "^10.9.1",

View File

@ -16,7 +16,7 @@
import ts from 'typescript';
import { Code } from '../utils/constant';
import {
comment, Context, ISourceCodeProcessor, LogReporter, ModifyLogResult, ErrorInfo,
comment, Context, ISourceCodeProcessor, ModifyLogResult, ErrorInfo,
ProcessResult, sourceParser, JSDocModifyType, MethodNodeType, ApiSplitProcessorInterface
} from './typedef';
import { CommentHelper, LogResult } from './coreImpls';
@ -26,7 +26,7 @@ export class ApiSplitProcessor implements ISourceCodeProcessor, sourceParser.ITr
context?: Context;
process(context: Context, content: string): ProcessResult {
async process(context: Context, content: string): Promise<ProcessResult> {
if (!context.getOptions().splitUnionTypeApi) {
return { code: Code.OK, content: content };
}

View File

@ -26,7 +26,7 @@ export class AsynchronousFunctionProcessor implements ISourceCodeProcessor {
context?: Context;
process(context: Context, content: string): ProcessResult {
async process(context: Context, content: string): Promise<ProcessResult> {
const sourceParser = context.getSourceParser(content);
this.context = context;
const sourceFile: ts.SourceFile | undefined = sourceParser.createSourceFile(content);

View File

@ -112,14 +112,13 @@ export class OutputFileHelper {
// 获取报告输出路径
static getLogReportFilePath(inputParam: InputParameter): string {
const fileTimeStamp = FileUtils.getFileTimeStamp();
const fileName = path.basename(inputParam.inputFilePath, '.d.ts');
if (inputParam.outputFilePath) {
const dirName = path.dirname(inputParam.outputFilePath);
return path.join(dirName, `${fileName}_${fileTimeStamp}.xlsx`);
return path.join(dirName, `${fileName}.xlsx`);
} else {
const dirName = path.dirname(inputParam.inputFilePath);
return path.join(dirName, `${fileName}_${fileTimeStamp}.xlsx`);
return path.join(dirName, `${fileName}.xlsx`);
}
}
}
@ -219,11 +218,11 @@ export class SourceCodeParserImpl extends sourceParser.SourceCodeParser {
const hasComment: boolean = currentCommentNode.commentInfos ? currentCommentNode.commentInfos.length > 0 : false;
const { line, character } = node.getSourceFile().getLineAndCharacterOfPosition(node.getStart());
if (thiz.shouldNotifyCallback(node, hasComment, onlyVisitHasComment)) {
LogUtil.d('SourceCodeParserImpl', `kind: ${node.kind}, line: ${line}, ${JSON.stringify(currentCommentNode.commentInfos)}`);
LogUtil.d('SourceCodeParserImpl', `kind: ${node.kind}, line: ${line + 1}, ${JSON.stringify(currentCommentNode.commentInfos)}`);
callback.onVisitNode(currentCommentNode);
} else {
LogUtil.d('SourceCodeParserImpl',
`skip, kind: ${node.kind}, line: ${line}, character: ${character}, commnet size: ${currentCommentNode.commentInfos?.length}`);
`skip, [ ${node.getText()} ] kind: ${node.kind}, line: ${line + 1}, character: ${character}, comment size: ${currentCommentNode.commentInfos?.length}`);
}
if (thiz.shouldForEachChildren(node)) {
node.forEachChild((child) => {
@ -798,6 +797,7 @@ export class InputParameter {
outputFilePath: string | undefined;
logLevel: string = '';
splitUnionTypeApi: boolean = false;
branch: string = 'master';
options: Options = new Options();
parse() {
@ -806,16 +806,20 @@ export class InputParameter {
.name('jsdoc-tool')
.description('CLI to format d.ts')
.version('0.1.0')
.allowUnknownOption(true)
.requiredOption('-i, --input <path>', `${StringResource.getString(StringResourceId.COMMAND_INPUT_DESCRIPTION)}`)
.option('-o, --output <path>', `${StringResource.getString(StringResourceId.COMMAND_OUT_DESCRIPTION)}`)
.option('-l, --logLevel <INFO,WARN,DEBUG,ERR>', `${StringResource.getString(StringResourceId.COMMAND_LOGLEVEL_DESCRIPTION)}`, 'INFO')
.option('-s, --split', `${StringResource.getString(StringResourceId.COMMAND_SPLIT_API)}`, false);
.option('-s, --split', `${StringResource.getString(StringResourceId.COMMAND_SPLIT_API)}`, false)
.option('-b, --branch <string>', `${StringResource.getString(StringResourceId.COMMAND_BRANCH)}`, 'master');
program.parse();
const options = program.opts();
this.inputFilePath = options.input;
this.outputFilePath = options.output;
this.logLevel = options.logLevel;
this.splitUnionTypeApi = options.split;
this.branch = options.branch;
this.checkInput();
}
@ -859,6 +863,7 @@ export class InputParameter {
}
}
this.options.splitUnionTypeApi = this.splitUnionTypeApi;
this.options.workingBranch = this.branch;
}
private checkFileExists(filePath: string) {
@ -953,16 +958,16 @@ export class LogReporterImpl implements LogReporter {
return this.modifyResultMap;
}
writeCheckResults(path: string): void {
this.writer?.writeResults(this.checkResults, undefined, path);
async writeCheckResults(path: string): Promise<void> {
await this.writer?.writeResults(this.checkResults, undefined, path);
}
writeModifyResults(path: string): void {
this.writer?.writeResults(undefined, this.modifyResults, path);
async writeModifyResults(path: string): Promise<void> {
await this.writer?.writeResults(undefined, this.modifyResults, path);
}
writeAllResults(path: string): void {
this.writer?.writeResults(this.checkResults, this.modifyResults, path);
async writeAllResults(path: string): Promise<void> {
await this.writer?.writeResults(this.checkResults, this.modifyResults, path);
}
}
@ -995,7 +1000,8 @@ export class ExcelWriter implements LogWriter {
});
}
writeResults(checkResults: Array<CheckLogResult> | undefined, modifyResults: Array<ModifyLogResult> | undefined, path: string): void {
async writeResults(checkResults: Array<CheckLogResult> | undefined,
modifyResults: Array<ModifyLogResult> | undefined, path: string): Promise<void> {
if (checkResults) {
const checkResultsSheet: excelJs.Worksheet = this.workBook.addWorksheet('待确认报告');
checkResultsSheet.columns = this.checkResultsColumns;
@ -1006,7 +1012,7 @@ export class ExcelWriter implements LogWriter {
modifyResultsSheet.columns = this.modifyResultsColumns;
modifyResultsSheet.addRows(modifyResults);
}
this.workBook.xlsx.writeFile(path);
await this.workBook.xlsx.writeFile(path);
}
}

View File

@ -30,27 +30,26 @@ import { Context, IJSDocModifier, ISourceCodeProcessor, LogReporter, ProcessResu
*/
export class JSDocModifierImpl implements IJSDocModifier {
tag: string = 'jsdoc-tool';
start(): void {
const inputParameter = new InputParameter();
try {
inputParameter.parse();
} catch (error) {
LogUtil.e(this.tag, error);
return;
}
this.startInternal(inputParameter);
async start(): Promise<void> {
await this.startInternal();
}
startInternal(inputParameter: InputParameter) {
LogUtil.logLevel = LogLevelUtil.get(inputParameter.logLevel);
const sourceProcessor: ISourceCodeProcessor = this.getSourceProcessor(inputParameter);
const baseContext: Context = this.getBaseContext(inputParameter);
LogUtil.i(this.tag, StringResource.getString(StringResourceId.START_MESSAGE));
const result: ProcessResult = sourceProcessor.process(baseContext, '');
if (result.code !== Code.OK) {
LogUtil.e(this.tag, result.content);
} else {
LogUtil.i(this.tag, result.content);
async startInternal(): Promise<void> {
try {
const inputParameter = new InputParameter();
inputParameter.parse();
LogUtil.logLevel = LogLevelUtil.get(inputParameter.logLevel);
const sourceProcessor: ISourceCodeProcessor = this.getSourceProcessor(inputParameter);
const baseContext: Context = this.getBaseContext(inputParameter);
LogUtil.i(this.tag, StringResource.getString(StringResourceId.START_MESSAGE));
const result: ProcessResult = await sourceProcessor.process(baseContext, '');
if (result.code !== Code.OK) {
LogUtil.e(this.tag, result.content);
} else {
LogUtil.i(this.tag, result.content);
}
} catch (error) {
LogUtil.e(this.tag, error);
}
}
@ -65,20 +64,6 @@ export class JSDocModifierImpl implements IJSDocModifier {
}
}
export class JSDOcModifierTestEntry extends JSDocModifierImpl {
runTest(inputFile: string, outputFile: string) {
const inputParameter = new InputParameter();
inputParameter.inputFilePath = inputFile;
inputParameter.outputFilePath = outputFile;
inputParameter.splitUnionTypeApi = true;
inputParameter.getOptions().splitUnionTypeApi = true;
this.startInternal(inputParameter);
}
}
abstract class BaseSourceCodeProcessor implements ISourceCodeProcessor {
inputParam: InputParameter;
@ -86,7 +71,7 @@ abstract class BaseSourceCodeProcessor implements ISourceCodeProcessor {
this.inputParam = inputParam;
}
abstract process(context: Context, code: string): ProcessResult;
abstract process(context: Context, code: string): Promise<ProcessResult>;
buildProcessorContext(parentContext: Context, inputFile: string): Context {
return new ContextImpl(inputFile,
@ -100,7 +85,7 @@ abstract class BaseSourceCodeProcessor implements ISourceCodeProcessor {
*/
export class SingleFileProcessor extends BaseSourceCodeProcessor {
process(context: Context, content: string): ProcessResult {
async process(context: Context, content: string): Promise<ProcessResult> {
const inputFilePath = context.getInputFile();
if (!inputFilePath) {
return {
@ -118,20 +103,20 @@ export class SingleFileProcessor extends BaseSourceCodeProcessor {
let preResult = {
code: Code.OK,
content: rawCodeStr!
content: rawCodeStr ? rawCodeStr : ''
};
const newContext = this.buildProcessorContext(context, context.getInputFile());
const logReporter: LogReporter = context.getLogReporter();
newContext.setLogReporter(logReporter);
for (let processor of processorRegistry) {
preResult = processor.process(newContext, preResult.content);
preResult = await processor.process(newContext, preResult.content);
if (preResult.code === Code.ERROR) {
break;
}
}
// 报告落盘
const reportFilePath: string = OutputFileHelper.getLogReportFilePath(this.inputParam);
context.getLogReporter().writeAllResults(reportFilePath);
await context.getLogReporter().writeAllResults(reportFilePath);
LogUtil.i('jsdoc-tool', `the report file is in ${reportFilePath}`);
if (preResult.code === Code.OK) {
preResult.content = `new d.ts file is ${newContext.getOutputFile()}`;
@ -144,7 +129,7 @@ export class SingleFileProcessor extends BaseSourceCodeProcessor {
*
*/
export class MultiFileProcessor extends BaseSourceCodeProcessor {
process(context: Context, content: string): ProcessResult {
async process(context: Context, content: string): Promise<ProcessResult> {
const intpuDir = context.getInputFile();
if (!intpuDir) {
return {
@ -157,14 +142,14 @@ export class MultiFileProcessor extends BaseSourceCodeProcessor {
});
const errorSet: Array<ProcessResult> = new Array();
const logReporter: LogReporter = context.getLogReporter();
allSourceFiles.forEach((childFile) => {
for (const childFile of allSourceFiles) {
const rawCodeStr = FileUtils.readFileContent(childFile);
if (StringUtils.isEmpty(rawCodeStr)) {
errorSet.push({
code: Code.ERROR,
content: `${childFile}: ${StringResource.getString(StringResourceId.INPUT_FILE_CONTENT_EMPTY)}`
});
return;
continue;
}
const newContext = this.buildProcessorContext(context, childFile);
newContext.setLogReporter(logReporter);
@ -174,21 +159,21 @@ export class MultiFileProcessor extends BaseSourceCodeProcessor {
};
for (let processor of processorRegistry) {
preValue = processor.process(newContext, preValue.content);
preValue = await processor.process(newContext, preValue.content);
if (preValue.code !== Code.OK) {
errorSet.push(preValue);
break;
}
}
});
}
// 报告落盘
const reportFilePath: string = OutputFileHelper.getLogReportFilePath(this.inputParam);
context.getLogReporter().writeAllResults(reportFilePath);
await context.getLogReporter().writeAllResults(reportFilePath);
LogUtil.i('jsdoc-tool', `the report file is in ${reportFilePath}`);
return {
code: errorSet.length > 0 ? Code.ERROR : Code.OK,
content: errorSet.length > 0 ? JSON.stringify(errorSet)
: `new d.ts file is in ${OutputFileHelper.getMultiOutputDir(this.inputParam)}`
content: errorSet.length > 0 ? JSON.stringify(errorSet) :
`new d.ts file is in ${OutputFileHelper.getMultiOutputDir(this.inputParam)}`
};
}
}

View File

@ -32,11 +32,12 @@ export class CommentModificationProcessor implements ISourceCodeProcessor {
context?: Context;
rawSourceCodeInfo?: rawInfo.RawSourceCodeInfo;
process(context: Context, content: string): ProcessResult {
async process(context: Context, content: string): Promise<ProcessResult> {
this.context = context;
const newParser = context.getSourceParser(content);
this.logReporter = context.getLogReporter();
this.rawSourceCodeInfo = context.getRawSourceCodeInfo();
await apiChecker.initEnv(context.getOptions().workingBranch);
const newSourceFile = newParser.visitEachNodeComment(this, false);
return {
code: Code.OK,
@ -48,7 +49,7 @@ export class CommentModificationProcessor implements ISourceCodeProcessor {
if (node.astNode) {
const curNode: ts.Node = node.astNode;
// 获取诊断信息
const checkResults = apiChecker.checkJSDoc(node.astNode, node.astNode?.getSourceFile());
const checkResults = apiChecker.checkJSDoc(node.astNode, node.astNode?.getSourceFile(), this.context?.getInputFile());
const newCommentIndexs: number[] = [];
const newCommentInfos: comment.CommentInfo[] = node.commentInfos ? [...node.commentInfos] : [];
// 获取需要整改的JSDoc数组
@ -95,10 +96,17 @@ export class CommentModificationProcessor implements ISourceCodeProcessor {
const modifyResult: boolean = JSDocModificationManager.addTagFrommParentNode(node, commentInfo, tagName,
this.context);
if (modifyResult) {
const modifyLogResult: ModifyLogResult = LogResult.createModifyResult(curNode, newCommentInfos,
JSDocModificationManager.createErrorInfo(ErrorInfo.COMPLETE_INHERIT_TAG_INFORMATION, [`${jsdocNumber}`, `${tagName}`]),
this.context, apiName, JSDocModifyType.MISSING_TAG_COMPLETION);
this.logReporter?.addModifyResult(modifyLogResult);
if (tagName === 'permission') {
const checkLogResult: CheckLogResult = LogResult.createCheckResult(curNode, newCommentInfos,
JSDocModificationManager.createErrorInfo(ErrorInfo.COMPLETE_INHERIT_PERMISSION_TAG_ERROR, [`${jsdocNumber}`, `${tagName}`]),
this.context, apiName, JSDocCheckErrorType.INCOMPLETE_TAG);
this.logReporter?.addCheckResult(checkLogResult);
} else {
const modifyLogResult: ModifyLogResult = LogResult.createModifyResult(curNode, newCommentInfos,
JSDocModificationManager.createErrorInfo(ErrorInfo.COMPLETE_INHERIT_TAG_INFORMATION, [`${jsdocNumber}`, `${tagName}`]),
this.context, apiName, JSDocModifyType.MISSING_TAG_COMPLETION);
this.logReporter?.addModifyResult(modifyLogResult);
}
}
}
});
@ -120,11 +128,13 @@ export class CommentModificationProcessor implements ISourceCodeProcessor {
}
if (!modifier || !modifyResult) {
let modifyErrorInfo: string = ErrorInfo.COMPLETE_TAG_ERROR;
let insteadInfo: string[] = [`${jsdocNumber}`, `${tagName}`];
if (tagName === 'interface') {
modifyErrorInfo = ErrorInfo.COMPLETE_INTERFACE_TAG_ERROR;
insteadInfo = [`${jsdocNumber}`];
}
const checkLogResult: CheckLogResult = LogResult.createCheckResult(curNode, newCommentInfos,
JSDocModificationManager.createErrorInfo(modifyErrorInfo, [`${jsdocNumber}`, `${tagName}`]),
JSDocModificationManager.createErrorInfo(modifyErrorInfo, insteadInfo),
this.context, apiName, JSDocCheckErrorType.INCOMPLETE_TAG);
this.logReporter?.addCheckResult(checkLogResult);
}
@ -241,7 +251,9 @@ class JSDocModificationManager {
if (node.parentNode) {
const pTag: comment.CommentTag | undefined = getParentTag(node.parentNode, tagName);
if (pTag) {
commentInfo.commentTags.push(pTag);
if (tagName !== 'permission') {
commentInfo.commentTags.push(pTag);
}
return true;
}
}
@ -298,7 +310,7 @@ class JSDocModificationManager {
commentInfo.commentTags.push(newCommentTag);
// 提示description缺失信息
const checkLogResult: CheckLogResult = LogResult.createCheckResult(node.astNode, commentInfos,
JSDocModificationManager.createErrorInfo(ErrorInfo.PARAM_FORAMT_DESCRIPTION_ERROR, [`${i + 1}`]), context, apiName,
JSDocModificationManager.createErrorInfo(ErrorInfo.PARAM_FORAMT_DESCRIPTION_ERROR, [`${curIndex + 1}`]), context, apiName,
JSDocCheckErrorType.PARAM_DESCRIPTION_WARNING);
context?.getLogReporter().addCheckResult(checkLogResult);
}
@ -364,7 +376,6 @@ class JSDocModificationManager {
const jsDocModifier: Map<string, JsDocModificationInterface> = new Map([
['constant', JSDocModificationManager.addTagWithoutValue],
['deprecated', JSDocModificationManager.addTagFrommParentNode],
['enum', JSDocModificationManager.addTagWithValue],
['extends', JSDocModificationManager.addTagWithValue],
['famodelonly', JSDocModificationManager.addTagFrommParentNode],
['namespace', JSDocModificationManager.addTagWithValue],
@ -382,10 +393,10 @@ const jsDocModifier: Map<string, JsDocModificationInterface> = new Map([
const JSDOC_ORDER_TAGS_ARRAY = [
'namespace', 'extends', 'typedef', 'interface', 'permission', 'enum', 'constant', 'type', 'param', 'default',
'returns', 'readonly', 'throws', 'static', 'fires', 'syscap', 'systemapi', 'famodelonly', 'FAModelOnly',
'stagemodelonly', 'StageModelOnly', 'crossplatform', 'since', 'deprecated', 'useinstead', 'form', 'example'
'stagemodelonly', 'StageModelOnly', 'crossplatform', 'since', 'deprecated', 'useinstead', 'test', 'form', 'example'
];
/**
*
*/
const INHERIT_TAGS_ARRAY = ['deprecated', 'famodelonly', 'stagemodelonly', 'systemapi', 'test'];
const INHERIT_TAGS_ARRAY = ['deprecated', 'famodelonly', 'stagemodelonly', 'systemapi', 'test', 'permission'];

View File

@ -22,7 +22,7 @@ import { CommentHelper } from './coreImpls';
import { comment, Context, ISourceCodeProcessor, ProcessResult, sourceParser } from './typedef';
export class OutputProcessor implements ISourceCodeProcessor {
process(context: Context, content: string): ProcessResult {
async process(context: Context, content: string): Promise<ProcessResult> {
try {
let outputContent = content;
const formater = new Formatter(content);
@ -36,7 +36,7 @@ export class OutputProcessor implements ISourceCodeProcessor {
FileUtils.writeStringToFile(outputContent, context.getOutputFile());
return { code: Code.OK, content: outputContent };
} catch(error) {
} catch (error) {
LogUtil.e('OutputProcessor', `error: ${context.getInputFile()}, ${error}`);
return { code: Code.OK, content: content };
}
@ -69,7 +69,7 @@ class Formatter implements sourceParser.INodeVisitorCallback {
class NumberLiteralCaseRule {
upperCase: boolean;
sourceParser: sourceParser.SourceCodeParser;
content: string|undefined;
content: string | undefined;
constructor(upperCase: boolean, sourceParser: sourceParser.SourceCodeParser) {
this.upperCase = upperCase;

View File

@ -5,7 +5,7 @@ import { comment, Context, ISourceCodeProcessor, ProcessResult, sourceParser } f
export class RawSourceCodeProcessor implements ISourceCodeProcessor, sourceParser.INodeVisitorCallback {
rawSourceCodeInfo?: RawSourceCodeInfoImpl;
process(context: Context, content: string): ProcessResult {
async process(context: Context, content: string): Promise<ProcessResult> {
const sourceParser: sourceParser.SourceCodeParser = context.getSourceParser(content);
this.rawSourceCodeInfo = new RawSourceCodeInfoImpl(content);
sourceParser.visitEachNodeComment(this, false);

View File

@ -30,7 +30,7 @@ export interface ISourceCodeProcessor {
*
* @param content
*/
process(context: Context, content: string): ProcessResult;
process(context: Context, content: string): Promise<ProcessResult>;
}
/**
@ -101,8 +101,8 @@ export class Options {
commentOptions = {
emptyLineUnderDescrition: true
};
splitUnionTypeApi: boolean = false;
workingBranch: string = 'master';
}
@ -616,19 +616,19 @@ export interface LogReporter {
*
* @param path
*/
writeCheckResults(path: string): void;
writeCheckResults(path: string): Promise<void>;
/**
*
* @param path
*/
writeModifyResults(path: string): void;
writeModifyResults(path: string): Promise<void>;
/**
*
* @param path
*/
writeAllResults(path: string): void;
writeAllResults(path: string): Promise<void>;
/**
* writer对象
@ -693,7 +693,7 @@ export interface LogWriter {
* @param modifyResults
* @param path
*/
writeResults(checkResults: Array<CheckLogResult> | undefined, modifyResults: Array<ModifyLogResult> | undefined, path: string): void;
writeResults(checkResults: Array<CheckLogResult> | undefined, modifyResults: Array<ModifyLogResult> | undefined, path: string): Promise<void>;
}
/**
@ -784,7 +784,7 @@ export namespace sourceParser {
* API
*/
export interface IJSDocModifier {
start(): void;
start(): Promise<void>;
}
interface OrderResultInfo {
@ -819,11 +819,12 @@ export enum ErrorInfo {
PARAM_FORAMT_DESCRIPTION_ERROR = '请自行添加第[$$]个参数的描述信息.',
COMPLETE_TAG_INFORMATION = '补全第[$$]段JSDoc的@$$标签.',
COMPLETE_INHERIT_TAG_INFORMATION = '第[$$]段JSDoc从父类继承@$$标签.',
COMPLETE_TAG_ERROR = '第[$$]段JSDoc缺少@$$标签, 请自行确认修改.',
COMPLETE_INTERFACE_TAG_ERROR = '第[$$]段JSDoc缺少@$$标签, 请自行确认补全@interface或者@type标签.',
COMPLETE_INHERIT_PERMISSION_TAG_ERROR = '第[$$]段JSDoc父类存在@$$标签, 请自行确认并补全相同标签.',
COMPLETE_TAG_ERROR = '第[$$]段JSDoc缺少@$$标签, 请自行确认并修改.',
COMPLETE_INTERFACE_TAG_ERROR = '第[$$]段JSDoc缺少@interface或@typedef标签, 请自行确认并补全@interface或@typedef标签.',
MODIFY_TAG_ORDER_INFORMATION = '第[$$]段JSDoc标签顺序调整.',
JSDOC_FORMAT_ERROR = 'JSDoc格式错误, 请检查.',
JSDOC_ILLEGAL_ERROR = '第[$$]段JSDoc校验失败: ',
JSDOC_ILLEGAL_ERROR = '第[$$]段JSDoc校验失败: \n',
EVENT_SUBSCRIPTION_SPLITTION = '对事件订阅函数[$$]进行了拆分.',
AYYNCHRONOUS_FUNCTION_JSDOC_COPY = '对异步函数[$$]进行了JSDoc复制.'
}

View File

@ -15,10 +15,39 @@
import { JSDocModifierImpl } from './core/entry';
import { IJSDocModifier } from './core/typedef';
import { ConstantValue, StringResourceId } from './utils/constant';
import { StringResource, StringUtils } from './utils/stringUtils';
function main() {
checkEnvVersion();
const jsDocModifier: IJSDocModifier = new JSDocModifierImpl();
jsDocModifier.start();
}
function checkEnvVersion(): void {
const version = process.version;
const versionRegExp = /^v(\d+)\.(\d+)\.(\d+).*/;
const matchArray = version.match(versionRegExp);
const requiredVersions = [ConstantValue.MAJOR_V, ConstantValue.MINOR_V, ConstantValue.PATCH_V];
let showVersionWarning = true;
if (matchArray && matchArray.length === 4) {
for (let index = 0; index < 3; index++) {
const curV = Number(matchArray[index + 1]);
const requiredV = requiredVersions[index];
if (curV > requiredV || curV < requiredV) {
showVersionWarning = curV > requiredV;
break;
} else {
continue;
}
}
}
if (showVersionWarning) {
return;
}
let hintMessage = StringResource.getString(StringResourceId.VERSION_HINT);
hintMessage = StringUtils.formatString(hintMessage, requiredVersions);
console.warn('jsdoc-tool:', hintMessage);
}
main();

View File

@ -39,7 +39,9 @@ export enum StringResourceId {
OUTPUT_MUST_DIR,
OUTPUT_SAME_WITH_INPUT,
OUTPUT_SUBDIR_INPUT,
START_MESSAGE
START_MESSAGE,
COMMAND_BRANCH,
VERSION_HINT
}
export enum Instruct {
@ -56,4 +58,19 @@ export class ConstantValue {
* d.ts文件后缀名
*/
static DTS_EXTENSION = '.d.ts';
/**
* nodejs
*/
static MAJOR_V = 15;
/**
* nodejs
*/
static MINOR_V = 0;
/**
* nodejs
*/
static PATCH_V = 0;
}

View File

@ -14,15 +14,15 @@
*/
export enum LogLevel {
DEBUG = 0,
INFO = 1,
WARN = 2,
ERR = 3
DEBUG,
INFO,
WARN,
ERR
}
export class LogLevelUtil {
static get(level: string): LogLevel {
for (let v = LogLevel.INFO; v <= LogLevel.ERR; v++) {
for (let v = LogLevel.DEBUG; v <= LogLevel.ERR; v++) {
if (level === LogLevel[v]) {
return v;
}

View File

@ -30,6 +30,8 @@ const ZH_STRING_MAP: Map<number, string> = new Map([
[StringResourceId.OUTPUT_SAME_WITH_INPUT, '输出文件路径与输入文件路径相同'],
[StringResourceId.OUTPUT_SUBDIR_INPUT, '输出目录不能是输入目录的子目录'],
[StringResourceId.START_MESSAGE, '正在处理, 请稍后 ...'],
[StringResourceId.COMMAND_BRANCH, 'OpenHarmony 分支名'],
[StringResourceId.VERSION_HINT, '告警, 需要nodejs $0.$1.$2+']
]);
const EN_STRING_MAP: Map<number, string> = new Map([
@ -47,6 +49,8 @@ const EN_STRING_MAP: Map<number, string> = new Map([
[StringResourceId.OUTPUT_SAME_WITH_INPUT, 'the output file path is same as the input file path'],
[StringResourceId.OUTPUT_SUBDIR_INPUT, 'the output directory cannot be a subdirectory of the input directory'],
[StringResourceId.START_MESSAGE, 'Processing please wait ...'],
[StringResourceId.COMMAND_BRANCH, 'OpenHarmony branch name'],
[StringResourceId.VERSION_HINT, 'warning, nodejs version $0.$1.$2+ is required']
]);
export class StringResource {
@ -61,13 +65,21 @@ export class StringUtils {
return str === undefined || str.length === 0;
}
static hasSubstring(str: string, sub: string|RegExp): boolean {
static hasSubstring(str: string, sub: string | RegExp): boolean {
return str.search(sub) !== -1;
}
static replaceAt(src: string, index: number, replacement: string) {
return src.substring(0, index) + replacement + src.substring(index + replacement.length);
}
static formatString(pattern: string, args: Array<any>): string {
let newStr = pattern;
for (let index = 0; index < args.length; index++) {
newStr = newStr.replace(`$${index}`, `${args[index]}`);
}
return newStr;
}
}
export class LogReportStringUtils {

View File

@ -0,0 +1,24 @@
/*
* Copyright (c) 2023 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.
*/
{
"require": [
"test/mocha/init.js"
],
"spec": [
"test/testCase/*.ts"
],
"reporter": "mochawesome"
}

View File

@ -0,0 +1,18 @@
/*
* Copyright (c) 2023 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.
*/
require("ts-node").register({
project: "tsconfig.json"
});

View File

@ -0,0 +1,121 @@
import { expect } from 'chai';
import path from 'path';
import fs from 'fs';
import { JSDocModifierImpl } from '../../src/core/entry';
describe('testSingleFile', function () {
const testFileDir = path.join(__dirname, '..', '/ut/');
const outFileDir = path.join(__dirname, '..', '/output/testSingleFile/');
const expectFileDir = path.join(__dirname, '..', '/expect/');
const testFileNames = fs.readdirSync(testFileDir);
const argLen = process.argv.length;
testFileNames.forEach((testFileName) => {
const testFilePath = path.join(testFileDir, testFileName);
const outputFilePath = path.join(outFileDir, testFileName);
const expectFilePath = path.join(expectFileDir, testFileName);
it('testFile#' + testFilePath, async function () {
if (fs.existsSync(outputFilePath)) {
fs.rmSync(outputFilePath);
}
const inputParams = [];
inputParams.push('-s');
inputParams.push('-i');
inputParams.push(testFilePath);
inputParams.push('-o');
inputParams.push(outputFilePath);
process.argv.splice(argLen, inputParams.length, ...inputParams);
const testEntry = new JSDocModifierImpl();
await testEntry.start();
const outputFileContent: string = fs.readFileSync(outputFilePath, 'utf-8').replace(/\r\n/g, '\n');
const expectFileContent: string = fs.readFileSync(expectFilePath, 'utf-8').replace(/\r\n/g, '\n');
expect(outputFileContent).eql(expectFileContent);
});
});
});
describe('testMultiFiles', function () {
const testFileDir = path.join(__dirname, '..', '/ut/');
const outFileDir = path.join(__dirname, '..', '/output/testMultiFiles/');
const expectFileDir = path.join(__dirname, '..', '/expect/');
before(async function () {
this.timeout(10000);
const inputParams = [];
inputParams.push('-s');
inputParams.push('-i');
inputParams.push(testFileDir);
inputParams.push('-o');
inputParams.push(outFileDir);
process.argv.splice(process.argv.length, 0, ...inputParams);
const testEntry = new JSDocModifierImpl();
await testEntry.start();
});
const testFileNames = fs.readdirSync(testFileDir);
testFileNames.forEach((testFileName) => {
const testFilePath = path.join(testFileDir, testFileName);
const outputFilePath = path.join(outFileDir, testFileName);
const expectFilePath = path.join(expectFileDir, testFileName);
it('testDir#' + testFilePath, function () {
const outputFileContent: string = fs.readFileSync(outputFilePath, 'utf-8').replace(/\r\n/g, '\n');
const expectFileContent: string = fs.readFileSync(expectFilePath, 'utf-8').replace(/\r\n/g, '\n');
expect(outputFileContent).eql(expectFileContent);
});
});
});
describe('testBundleSingleFile', function () {
const testFileDir = path.join(__dirname, '..', '/ut/');
const outFileDir = path.join(__dirname, '..', '/output/testBundleSingleFile/');
const expectFileDir = path.join(__dirname, '..', '/expect/');
const testFileNames = fs.readdirSync(testFileDir);
const nodeExecute = process.execPath;
const { execFileSync } = require('child_process');
testFileNames.forEach((testFileName) => {
const testFilePath = path.join(testFileDir, testFileName);
const outputFilePath = path.join(outFileDir, testFileName);
const expectFilePath = path.join(expectFileDir, testFileName);
it('bundle_file#' + testFileName, function () {
if (fs.existsSync(outputFilePath)) {
fs.rmSync(outputFilePath);
}
execFileSync(nodeExecute, [
'build/bundle.js', '-s',
'-i', `${testFilePath}`,
'-o', `${outputFilePath}`
]);
const outputFileContent: string = fs.readFileSync(outputFilePath, 'utf-8').replace(/\r\n/g, '\n');
const expectFileContent: string = fs.readFileSync(expectFilePath, 'utf-8').replace(/\r\n/g, '\n');
expect(outputFileContent).eql(expectFileContent);
});
});
});
describe('testBundleMultiFiles', function () {
const testFileDir = path.join(__dirname, '..', '/ut/');
const outFileDir = path.join(__dirname, '..', '/output/testBundleMultiFiles/');
const expectFileDir = path.join(__dirname, '..', '/expect/');
const nodeExecute = process.execPath;
const { execFileSync } = require('child_process');
before(function () {
this.timeout(10000);
execFileSync(nodeExecute, [
'build/bundle.js', '-s',
'-i', `${testFileDir}`,
'-o', `${outFileDir}`
]);
});
const testFileNames = fs.readdirSync(testFileDir);
testFileNames.forEach((testFileName) => {
const outputFilePath = path.join(outFileDir, testFileName);
const expectFilePath = path.join(expectFileDir, testFileName);
it('bundle_dir#' + testFileName, function () {
const outputFileContent: string = fs.readFileSync(outputFilePath, 'utf-8').replace(/\r\n/g, '\n');
const expectFileContent: string = fs.readFileSync(expectFilePath, 'utf-8').replace(/\r\n/g, '\n');
expect(outputFileContent).eql(expectFileContent);
});
});
});

View File

@ -13,10 +13,10 @@
* limitations under the License.
*/
const path = require("path");
const TerserPlugin = require("terser-webpack-plugin");
const webpack = require("webpack");
const packageInfo = require('./package.json')
const path = require('path');
const TerserPlugin = require('terser-webpack-plugin');
const webpack = require('webpack');
const packageInfo = require('./package.json');
module.exports = (env, argv) => {
const config = {
@ -39,12 +39,11 @@ module.exports = (env, argv) => {
}
},
{
test: /\.json$/,
include: path.resolve(__dirname, 'src'),
loader: 'json-loader',
exclude: [
/node_modules/,
/test/
test: /build\.json$/,
use: [
{
loader: path.resolve(__dirname, 'loader/flavor.js')
}
]
}
]
@ -67,6 +66,6 @@ module.exports = (env, argv) => {
entryOnly: true
})
]
}
};
return config;
}
};