ts语法检查工具

Signed-off-by: fanjiaojiao0729 <fanjiaojiao@huawei.com>
This commit is contained in:
fanjiaojiao0729 2023-10-19 17:15:58 +08:00
parent f853ff6b5b
commit 212d803f6f
6 changed files with 260 additions and 11 deletions

View File

@ -15,8 +15,10 @@
import path from 'path';
import { ApiResultSimpleInfo } from '../../typedef/checker/result_type';
import { Check } from './src/api_check_plugin';
import { TsSyntaxCheck } from './src/ts_check_plugin';
import { FileUtils } from '../../utils/FileUtils';
import { LogUtil } from '../../utils/logUtil';
import { GenerateFile } from '../../utils/checkUtils';
/**
* local entrance
@ -26,10 +28,12 @@ export class LocalEntry {
const mdFilesPath = path.resolve(FileUtils.getBaseDirName(), '../mdFiles.txt');
let result: ApiResultSimpleInfo[] = [];
try {
result = Check.scanEntry(mdFilesPath);
result = TsSyntaxCheck.checkAPISyntax(mdFilesPath);
result.concat(Check.scanEntry(mdFilesPath));
} catch (error) {
LogUtil.e('API_CHECK_ERROR', error);
} finally {
GenerateFile.writeFile(result, '../result.txt', {});
}
return result;
}

View File

@ -28,11 +28,11 @@ export class AddErrorLogs {
* @param { string } apiName -error message api name
* @param { string } apiFullText -error message api text
* @param { string } message -error infomation
* @param { ApiResultSimpleInfo[] } checkErrorArr -array for storing error information
* @param { ApiResultSimpleInfo[] } checkErrorInfos -array for storing error information
*/
static addAPICheckErrorLogs(id: number, level: number, filePath: string, location: string, errorType: string,
apiType: string, version: number, apiName: string, apiFullText: string,
message: string, checkErrorArr: ApiResultSimpleInfo[]): void {
message: string, checkErrorInfos: ApiResultSimpleInfo[], checkErrorAllInfos: ApiResultInfo[]): void {
const apiChecktSimpleErrorLog: ApiResultSimpleInfo = new ApiResultSimpleInfo();
apiChecktSimpleErrorLog
.setID(id)
@ -50,8 +50,10 @@ export class AddErrorLogs {
.setVersion(version)
.setLevel(level)
.setApiName(apiName)
.setApiFullText(apiFullText);
checkErrorArr.push(apiChecktSimpleErrorLog);
.setApiFullText(apiFullText)
.setBaseName(filePath.substring(filePath.lastIndexOf('/') + 1, filePath.length));
checkErrorInfos.push(apiChecktSimpleErrorLog);
checkErrorAllInfos.push(apiCheckErrorLog);
}
}
}

View File

@ -0,0 +1,97 @@
/*
* 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.
*/
import fs from 'fs';
import path from 'path';
import ts from 'typescript';
import { Check } from './api_check_plugin';
import { AddErrorLogs } from './compile_info';
import { ApiResultSimpleInfo, ErrorType, ErrorID, LogType, ErrorLevel, ApiResultInfo } from
'../../../typedef/checker/result_type';
import { StringConstant } from '../../../utils/Constant';
import { PosOfNode, CompolerOptions, ObtainFullPath, GenerateFile } from '../../../utils/checkUtils';
export class TsSyntaxCheck {
/**
* ts语法检查工具入口
* @param { string } url -File path for storing critical information.
*/
static checkAPISyntax(url: string): ApiResultSimpleInfo[] {
const tsResult: ApiResultSimpleInfo[] = [];
const tsLocalResult: ApiResultInfo[] = [];
if (fs.existsSync(url)) {
const fullFilesPath: string[] = [];
ObtainFullPath.getFullFiles(path.resolve(__dirname, '../../../../../../api'), fullFilesPath);
const files: Array<string> = Check.getMdFiles(url);
const program: ts.Program = ts.createProgram({
rootNames: fullFilesPath,
options: CompolerOptions.getCompolerOptions()
});
//ts存在bug仅为解决无法绑定sourcefile根节点的问题
program.getTypeChecker();
const programSourceFiles: readonly ts.SourceFile[] = program.getSourceFiles();
const host: ts.CompilerHost = ts.createCompilerHost(CompolerOptions.getCompolerOptions());
const diagnostics: ts.Diagnostic[] = ts.runArkTSLinter(program, host);
files.forEach((filePath: string, index: number) => {
TsSyntaxCheck.checkAPISyntaxCallback(filePath, program, diagnostics, programSourceFiles, tsResult, tsLocalResult);
console.log(`scaning file in no ${++index}!`);
});
}
GenerateFile.writeExcelFile(tsLocalResult);
return tsResult;
}
/**
* ts检查主要处理过程
* @param { string } fileName
* @param { ts.Program } program
* @param { ts.Diagnostic[] } diagnostics
* @param { readonly ts.SourceFile[] } programSourceFiles
* @param { ApiResultSimpleInfo[] } tsResult
* @param { ApiResultInfo[] } checkErrorAllInfos
*/
static checkAPISyntaxCallback(fileName: string, program: ts.Program, diagnostics: ts.Diagnostic[],
programSourceFiles: readonly ts.SourceFile[], tsResult: ApiResultSimpleInfo[],
checkErrorAllInfos: ApiResultInfo[]): void {
const fileContent: string = fs.readFileSync(fileName, StringConstant.UTF8);
const node: ts.Node = ts.createSourceFile(fileName, fileContent, ts.ScriptTarget.ES2017, true);
const fileSuffix: string = fileName.substring(fileName.lastIndexOf('.'), fileName.length);
// tsc诊断日志
if (fileSuffix === '.ts') {
const targetSourceFile: ts.SourceFile = node.getSourceFile();
programSourceFiles.forEach(programSourceFile => {
if (programSourceFile.fileName === targetSourceFile.fileName) {
const result: readonly ts.Diagnostic[] = program.getSemanticDiagnostics(programSourceFile);
result.forEach(item => {
AddErrorLogs.addAPICheckErrorLogs(ErrorID.TS_SYNTAX_ERROR_ID, ErrorLevel.MIDDLE,
item.file?.fileName as string, PosOfNode.getPosOfNode(node, item),
ErrorType.TS_SYNTAX_ERROR, LogType.LOG_API, -1, 'NA', 'NA', item.messageText as string, tsResult,
checkErrorAllInfos);
});
}
});
}
// ArkTS诊断日志
if (fileSuffix === '.ets') {
diagnostics.forEach(item => {
if (path.normalize(item.file?.fileName as string) === path.normalize(fileName)) {
AddErrorLogs.addAPICheckErrorLogs(ErrorID.TS_SYNTAX_ERROR_ID, ErrorLevel.MIDDLE,
item.file?.fileName as string, PosOfNode.getPosOfNode(node, item),
ErrorType.TS_SYNTAX_ERROR, LogType.LOG_API, -1, 'NA', 'NA', item.messageText as string, tsResult,
checkErrorAllInfos);
}
});
}
}
}

View File

@ -47,7 +47,8 @@ export enum ErrorType {
PARAMETER_ERRORS = 'wrong parameter',
API_PAIR_ERRORS = 'limited api pair errors',
ILLEGAL_ANY = 'illegal any',
API_CHANGE_ERRORS = 'api change errors'
API_CHANGE_ERRORS = 'api change errors',
TS_SYNTAX_ERROR = 'TS syntax error'
}
/**
@ -67,7 +68,8 @@ export enum ErrorID {
PARAMETER_ERRORS_ID = 9,
API_PAIR_ERRORS_ID = 10,
ILLEGAL_ANY_ID = 11,
API_CHANGE_ERRORS_ID = 12
API_CHANGE_ERRORS_ID = 12,
TS_SYNTAX_ERROR_ID = 13
}
/**
@ -211,6 +213,7 @@ export class ApiResultInfo {
level: number = -1;
apiName: string = '';
apiFullText: string = '';
baseName: string = '';
setErrorType(errorType: string): ApiResultInfo {
this.errorType = errorType;
@ -282,4 +285,12 @@ export class ApiResultInfo {
getApiFullText(): string {
return this.apiFullText;
}
setBaseName(baseName: string): ApiResultInfo {
this.baseName = baseName;
return this;
}
getBaseName(): string {
return this.baseName;
}
}

View File

@ -0,0 +1,135 @@
/*
* 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.
*/
import path from 'path';
import fs, { Stats } from 'fs';
import { Workbook, Worksheet } from 'exceljs';
import ts, { LineAndCharacter } from 'typescript';
import { ApiResultSimpleInfo, ApiResultInfo } from '../typedef/checker/result_type';
export class PosOfNode {
/**
*
* @param { ts.Node } node
* @param { ts.Diagnostic } diagnostic
*/
static getPosOfNode(node: ts.Node, diagnostic: ts.Diagnostic): string {
const posOfNode: LineAndCharacter = ts.getLineAndCharacterOfPosition(node.getSourceFile(),
diagnostic.start as number);
const location: string = diagnostic.file?.fileName as string +
`(line: ${posOfNode.line + 1}, col: ${posOfNode.character + 1})`;
return location;
}
}
export class CompolerOptions {
/**
* tsconfig配置项设置
*/
static getCompolerOptions(): ts.CompilerOptions {
const compilerOptions: ts.CompilerOptions = ts.readConfigFile(path.resolve(__dirname, '../../tsconfig.json'),
ts.sys.readFile).config.compilerOptions;
Object.assign(compilerOptions, {
target: 'es2020',
jsx: 'preserve',
incremental: undefined,
declaration: undefined,
declarationMap: undefined,
emitDeclarationOnly: undefined,
outFile: undefined,
composite: undefined,
tsBuildInfoFile: undefined,
noEmit: undefined,
isolatedModules: true,
paths: undefined,
rootDirs: undefined,
types: undefined,
out: undefined,
noLib: undefined,
noResolve: true,
noEmitOnError: undefined,
declarationDir: undefined,
suppressOutputPathCheck: true,
allowNonTsExtensions: true
});
return compilerOptions;
}
}
export class GenerateFile {
/**
* txt文件
* @param { ApiResultSimpleInfo[] } resultData
* @param { string } outputPath
* @param { string } option
*/
static writeFile(resultData: ApiResultSimpleInfo[], outputPath: string, option: object): void {
const STANDARD_INDENT: number = 2;
fs.writeFile(path.resolve(__dirname, outputPath), JSON.stringify(resultData, null, STANDARD_INDENT), option, (err) => {
if (err) {
console.error(`ERROR FOR CREATE FILE:${err}`);
} else {
console.log('API CHECK FINISH!');
}
});
}
/**
* excel文件
* @param { ApiResultInfo[] } apiCheckArr
*/
static async writeExcelFile(apiCheckArr: ApiResultInfo[]): Promise<void> {
const workbook: Workbook = new Workbook();
const sheet: Worksheet = workbook.addWorksheet('Js Api', { views: [{ xSplit: 1 }] });
sheet.getRow(1).values = ['order', 'errorType', 'fileName', 'apiName', 'apiContent', 'type', 'errorInfo', 'version', 'model'];
for (let i = 1; i <= apiCheckArr.length; i++) {
const apiData: ApiResultInfo = apiCheckArr[i - 1];
sheet.getRow(i + 1).values = [i, apiData.getErrorType(), apiData.getLocation(), apiData.getApiName(),
apiData.getApiFullText(), apiData.getApiType(), apiData.getMessage(), apiData.getVersion(), apiData.getBaseName()];
}
workbook.xlsx.writeBuffer().then((buffer) => {
fs.writeFile(path.resolve(__dirname, '../coreImpl/checker/Js_Api.xlsx'), buffer, function (err) {
if (err) {
console.error(err);
return;
}
});
});
}
}
export class ObtainFullPath {
/**
* api文件夹下的所有d.ts和d.ets路径
* @param { string } dir -api路径
* @param { string[] } utFiles -
*/
static getFullFiles(dir: string, utFiles: string[]): void {
try {
const files: string[] = fs.readdirSync(dir);
files.forEach((element) => {
const filePath: string = path.join(dir, element);
const status: Stats = fs.statSync(filePath);
if (status.isDirectory()) {
ObtainFullPath.getFullFiles(filePath, utFiles);
} else {
if (/\.d\.ts/.test(filePath) || /\.d\.ets/.test(filePath)) {
utFiles.push(filePath);
}
}
});
} catch (e) {
console.error('ETS ERROR: ' + e);
}
}
}

View File

@ -12,7 +12,7 @@
/* Language and Environment */
"target": "es2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
"lib": ["es2015","es2020"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
"experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
@ -96,6 +96,6 @@
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
// "skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}