add jsdoc_format_plugin

Signed-off-by: yangbo_404 <yangbo198@huawei.com>
Change-Id: Idf42f5b77eb4ccb20045a128d19ebd2119b9e771
This commit is contained in:
yangbo_404 2023-01-26 14:22:42 +08:00
parent e26cef7d8a
commit 4aa7e5f55b
7 changed files with 453 additions and 0 deletions

View File

@ -0,0 +1,14 @@
/*
* Copyright (c) 2021-2022 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.
*/

View File

@ -0,0 +1,26 @@
/*
* Copyright (c) 2021-2022 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.
*/
const decoratorSort = [
['description'],
['@namespace', '@extends', '@typedef', '@interface', '@permission', '@enum', '@constant'],
['@type', '@param', '@default'],
['@returns'],
['@readonly', '@throws'],
['@static'],
['@fires'],
['@syscap', '@systemapi', '@famodeonly', '@stagemodeonly', '@crossplatform', '@since', '@deprecated', '@useinstead', '@example']
]
exports.DECORATOR_SORT_LIST = decoratorSort;

View File

@ -0,0 +1,17 @@
{
"name": "jsdoc_format_plugin",
"version": "1.0.0",
"description": "",
"main": "collect.js",
"scripts": {
"test": "cd test && node test.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"exceljs": "^4.3.0",
"fs": "^0.0.1-security",
"path": "^0.12.7",
"typescript": "^4.7.4"
}
}

View File

@ -0,0 +1,2 @@
# api absolute path
Z:\root\interface\sdk-js\api\xxx.d.ts

View File

@ -0,0 +1,14 @@
/*
* Copyright (c) 2021-2022 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.
*/

View File

@ -0,0 +1,157 @@
/*
* Copyright (c) 2021-2022 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.
*/
const path = require('path');
const ts = require('typescript');
const { overwriteApiFile, getApiFiles, tsTransform, hasAPINote, getApiInfo, JSDOC_WRITELIST_SET, getAPINote,
hasCopyright, getParentApiInfo, TS_KEYWORD_SET } = require('./utils');
const formatedNodes = new Set([]);
let copyrightMessage = "";
function formatApiFile(url) {
return (context) => {
return (node) => {
sourceFile = node;
const fileName = url.replace(/\.d\.ts$/, ".new.d.ts");
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
const result = printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
overwriteApiFile(fileName, result, {});
tsTransform([fileName], formatJsDoc);
return node;
}
}
}
function checkJsDoc(node) {
if (hasAPINote(node)) {
if (!getApiInfo(node).version) {
return false;
}
}
return true;
}
function createNewJsdoc(node, docContent) {
// step1. get api information
let hasCR = false;
if (hasCopyright(docContent)) {
contents = docContent.split(/\*\//);
hasCR = true;
if (hasCopyright(contents[0])) {
copyrightMessage = contents[0] + "*/";
}
docContent = docContent.replace(copyrightMessage, "");
}
// 根据* @拆分jsDoc
const docs = docContent.split(/ *\* *\@/);
let docInfo = {};
docs.forEach((doc, docsIndex) => {
const lineInfos = doc.split(/[(\r\n)\r\n]+/);
let decoratorContent = "";
lineInfos.forEach((lineInfo) => {
const content = lineInfo.replace(/( *\/\*\* *)|( *\*\/ *)|( *\* *)/g, "");
if (content !== "") {
decoratorContent += `${content} `;
}
});
decoratorContent = decoratorContent.trim();
if (docsIndex === 0) {
docInfo["description"] = decoratorContent;
// 待补充全量报错场景
if (ts.isConstructorDeclaration(node)) {
docInfo["apiName"] = "constructor";
} else {
if (!node.name) {
console.log(node.getFullText())
}
docInfo["apiName"] = node.name.escapedText.toString();
}
} else {
const decoratorName = decoratorContent.match(/^[a-z]+\b/)[0];
if (JSDOC_WRITELIST_SET.has(decoratorName)) {
if (!docInfo[decoratorName]) {
docInfo[decoratorName] = [decoratorContent.replace(`${decoratorName} `, "")];
} else {
docInfo[decoratorName] = docInfo[decoratorName].push(decoratorContent.replace(`${decoratorName} `, ""));
}
}
}
});
docInfo = getParentApiInfo(node, docInfo);
// 缩进空格
const indentations = docContent.match(/(?<=\n) *(?=$)/g)[0];
// step2. create jsdoc
let newNodeFullText = "";
if (ts.isModuleDeclaration(node)) {
const docSystemapi = docInfo.systemapi && docInfo.systemapi[0] ? `${indentations + " * "}@systemapi\n` : "";
const docModel = docInfo.model && docInfo.model[0] ? `${indentations + " * "}@${docInfo.model[0]}\n` : "";
const docCrossplatform = docInfo.crossplatform && docInfo.crossplatform[0] ?
`${indentations + " * "}@crossplatform\n` : "";
const docDeprecated = docInfo.deprecated && docInfo.deprecated[0] ?
`${indentations + " * "}@deprecated ${docInfo.deprecated[0]}\n` : "";
const docUseinstead = docInfo.useinstead && docInfo.useinstead[0] ?
`${indentations + " * "}@useinstead ${docInfo.useinstead[0]}\n` : "";
const docPermission = docInfo.permission && docInfo.permission[0] ?
`${indentations + " * "}@useinstead ${docInfo.permission[0]}\n` : "";
newNodeFullText = `${indentations}/**\n` +
`${indentations + " * "}${docInfo.description}\n` +
`${indentations + " * "}@namespace ${docInfo.namespace ? docInfo.namespace : docInfo.apiName}\n` +
docPermission +
`${indentations + " * "}@syscap ${docInfo.syscap}\n` +
docSystemapi + docModel + docCrossplatform +
`${indentations + " * "}@since ${docInfo.since}\n` +
docDeprecated + docUseinstead +
`${indentations + " */"}\n${indentations}`;
if (hasCR) {
newNodeFullText = copyrightMessage + "\n" + newNodeFullText + node.getText();
}
}
return newNodeFullText;
}
function formatJsDoc(fileName) {
return (context) => {
return (node) => {
sourceFile = node;
node = ts.visitEachChild(node, processAllNodes, context);
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
const result = printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
overwriteApiFile(fileName, result, {});
return node;
}
function processAllNodes(node) {
if (hasAPINote(node) && !formatedNodes.has(fileName + node.pos + node.end) && !TS_KEYWORD_SET.has(node.kind)) {
formatedNodes.add(fileName + node.pos + node.end);
if (ts.isModuleDeclaration(node)) {
const result = createNewJsdoc(node, getAPINote(node));
console.log(result);
}
}
return ts.visitEachChild(node, processAllNodes, context);
}
}
}
function main(apiDir) {
const apiFiles = getApiFiles(apiDir);
tsTransform(apiFiles, formatApiFile);
}
const testDir = path.resolve(__dirname, "../plugin/api_file_path.txt");
main(testDir);

View File

@ -0,0 +1,223 @@
/*
* Copyright (c) 2021-2022 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.
*/
const fs = require('fs');
const path = require('path');
const ts = require('typescript');
/**
* Recursively query the api directory.
* @param {String} dir dir api directory
* @param {Array} apiFiles api files array
*/
function readDir(dir, apiFiles) {
const files = fs.readFileSync(dir);
files.forEach(element => {
const filePath = path.join(dir, element);
const status = fs.statSync(filePath);
if (status.isDirectory()) {
readDir(filePath, apiFiles);
} else if (status.isFile() && /\.d\.ts$/.test(filePath)) {
apiFiles.push(filePath);
}
});
}
/**
* Get api file array.
* @param {String} url api file(.d.ts) | api config file(.txt) | api file directory
* @returns api file array
*/
function getApiFiles(url) {
const apiFiles = [];
const urlStat = fs.statSync(url);
if (urlStat.isFile() && path.extname(url) === ".txt") {
const content = fs.readFileSync(url, "utf-8");
const apiFilesConfig = content.split(/[(\r\n)\r\n]+/);
apiFilesConfig.forEach(file => {
if (fs.existsSync(file)) {
apiFiles.push(file);
} else {
console.error(`FORMAT_ERROR: Api file of [${file}] is not found!`);
}
});
} else if (urlStat.isFile() && /\.d\.ts$/.test(url)) {
apiFiles.push(url);
} else if (url.isDirectory()) {
readDir(url, apiFiles);
}
return apiFiles;
}
exports.getApiFiles = getApiFiles;
/**
* Tsc compile engine.
* @param {Array} apiFiles api file array
* @param {Function} callback pretreatment function
*/
function tsTransform(apiFiles, callback) {
apiFiles.forEach(url => {
const content = fs.readFileSync(url, "utf-8");
const fileName = path.basename(url).replace(/\.d\.ts$/, "ts");
ts.transpileModule(content, {
compilerOptions: {
"target": ts.ScriptTarget.ES2017
},
fileName: fileName,
transformers: { before: [callback(url)] }
});
});
}
exports.tsTransform = tsTransform;
/**
* Create generated api file.
* @param {String} url generated api file path
* @param {String} content content generated api file content
* @param {Object} option option of writeFile(fs)
*/
function overwriteApiFile(url, content, option) {
fs.writeFileSync(url, content, option);
}
exports.overwriteApiFile = overwriteApiFile;
/**
* Get api note.
* @param {tsNode} node current node
* @returns api note
*/
function getAPINote(node) {
const apiLength = node.getText().length;
const apiFullLength = node.getFullText().length
return node.getFullText().substring(0, apiFullLength - apiLength);
}
exports.getAPINote = getAPINote;
/**
* Judge whether the api note exists.
* @param {tsNode} node current node
* @returns boolean
*/
function hasAPINote(node) {
const apiNote = getAPINote(node).replace(/[\s]/g, "");
if (apiNote && apiNote.length !== 0) {
return true;
}
return false;
}
exports.hasAPINote = hasAPINote;
/**
*
* @param {tsNode} node current node
* @returns boolean
*/
function hasCopyright(content) {
return /http\:\/\/www\.apache\.org\/licenses\/LICENSE\-2\.0/g.test(content);
}
exports.hasCopyright = hasCopyright;
/**
* Get api information.
* @param {tsNode} node current node
* @returns api information
*/
function getApiInfo(node) {
const notesStr = getAPINote(node)
let apiInfo = {};
if (notesStr !== "") {
if (/\@systemapi/g.test(notesStr)) {
apiInfo.systemapi = true;
}
if (/\@since\s*(\d+)/g.test(notesStr)) {
notesStr.replace(/\@since\s*(\d+)/g, (versionInfo) => {
apiInfo.since = versionInfo.replace(/\@since/g, '').trim();
})
}
if (/\@deprecated.*since\s*(\d+)/g.test(notesStr)) {
notesStr.replace(/\@deprecated.*since\s*(\d+)/g,
versionInfo => {
apiInfo.deprecated = versionInfo.replace(
/\@deprecated.*since\s*/g, '').trim();
})
}
if (/\@famodelonly/g.test(notesStr)) {
apiInfo.model = "famodelonly";
} else if (/\@stagemodelonly/g.test(notesStr)) {
apiInfo.model = "stagemodelonly";
}
if (/\@syscap\s*((\w|\.|\/|\{|\@|\}|\s)+)/g.test(notesStr)) {
notesStr.replace(/\@syscap\s*((\w|\.|\/|\{|\@|\}|\s)+)/g, sysCapInfo => {
apiInfo.syscap = sysCapInfo.replace(/\@syscap/g, '').trim();
})
}
if (/\@permission\s*((\w|\.|\/|\{|\@|\}|\s)+)/g.test(notesStr)) {
notesStr.replace(/\@permission\s*((\w|\.|\/|\{|\@|\}|\s)+)/g,
permissionInfo => {
apiInfo.permission =
permissionInfo.replace(/\@permission/g, '').trim();
})
}
}
return apiInfo;
}
exports.getApiInfo = getApiInfo;
function getParentApiInfo(node, apiInfo) {
if (!apiInfo.since) {
if (getApiInfo(node).since) {
apiInfo.since = [getApiInfo(node).since];
} else if (node.parent) {
apiInfo.since = [getParentApiInfo(node.parent, apiInfo).since];
} else {
apiInfo.since = ["NA"];
}
}
if (!apiInfo.model) {
if (getApiInfo(node).model) {
apiInfo.model = [getApiInfo(node).model];
} else if (node.parent) {
apiInfo.model = [getParentApiInfo(node.parent, apiInfo).model];
}
}
if (!apiInfo.systemapi) {
if (getApiInfo(node).systemapi) {
apiInfo.model = [getApiInfo(node).systemapi];
} else if (node.parent) {
apiInfo.model = [getParentApiInfo(node.parent, apiInfo).systemapi];
} else {
apiInfo.systemapi = [false];
}
}
if (!apiInfo.syscap) {
if (getApiInfo(node).syscap) {
apiInfo.model = [getApiInfo(node).syscap];
} else if (node.parent) {
apiInfo.model = [getParentApiInfo(node.parent, apiInfo).syscap];
} else {
apiInfo.systemapi = ["NA"];
}
}
return apiInfo;
}
exports.getParentApiInfo = getParentApiInfo;
const JSDOC_WRITELIST_SET = new Set(["constant", "crossplatform", "default", "deprecated", "enum", "example", "extends",
"famodeonly", "fires", "interface", "namespace", "param", "permission", "readonly", "returns", "since", "stagemodeonly",
"static", "syscap", "systemapi", "type", "typedef", "throws", "test", "useinstead"]);
exports.JSDOC_WRITELIST_SET = JSDOC_WRITELIST_SET;
const TS_KEYWORD_SET = new Set([ts.SyntaxKind.DeclareKeyword]);
exports.TS_KEYWORD_SET = TS_KEYWORD_SET;