[ADF-2236] Automatic check export (#2933)

* save export in a file

* print errors

* add position error

* color error log and add comment to skip a file

* export check ver 2

* ignore source

* fix export modules

* fix possible nullable value

* remove duplicates

* improve logs

* add travis configuration

* fix travis and import

* export fix

* remove export check js file

* add export check js ignore

* fix content metadata service export
This commit is contained in:
Eugenio Romano
2018-02-14 09:33:50 +00:00
committed by GitHub
parent c51d76f5a2
commit 8bfac2f9f8
7 changed files with 5534 additions and 3 deletions

2
.gitignore vendored
View File

@@ -21,3 +21,5 @@ package-lock.*
/ng2-components/ng2-alfresco-core/prebuilt-themes/
.ng_pkg_build/
/demo-shell/dist-dev-temp/
/lib/export-new.json
/lib/config/exportCheck.js

View File

@@ -62,6 +62,8 @@ jobs:
include:
- stage: Check 2.0.0 Project Update
script: ./scripts/test-e2e-bc.sh
- stage: Check ADF exports
script: cd lib && npm run test-export
# jobs:
# include:

354
lib/config/exportCheck.ts Normal file
View File

@@ -0,0 +1,354 @@
import * as ts from "typescript";
import * as fs from "fs";
import chalk from "chalk";
interface DocEntry {
position?: {
line: number,
character: number,
fileName: string
},
name?: string,
skipError?: boolean
};
let error_array = [];
let warning_array = [];
let exportedAllPath: Array<string> = [];
let classList: Array<any> = [];
let add_error = function (error: string, nameClass: string) {
let findErrorClass = false;
error_array.forEach((currentError) => {
if (currentError.nameClass === nameClass) {
findErrorClass = true;
return;
}
});
if (!findErrorClass) {
error_array.push({
error: error,
nameClass: nameClass
});
}
}
let count_error = 0;
let count_warning = 0;
let print_errors = function () {
error_array.forEach((current_error) => {
console.log(chalk.red(`[${++count_error}] ${current_error.error}\n`));
});
}
let add_warning = function (warning: string, nameClass: string, arrayCall: string[]) {
let findWarningClass = false;
warning_array.forEach((currentWarning) => {
if (currentWarning.nameClass === nameClass) {
findWarningClass = true;
return;
}
});
if (!findWarningClass) {
warning_array.push({
warning: warning,
nameClass: nameClass,
arrayCall: arrayCall
});
}
}
let print_warnings = function () {
warning_array.forEach((current_warning) => {
console.log(chalk.yellow(`[${++count_warning}] ${current_warning.warning} \n ${current_warning.arrayCall} \n`));
});
}
let currentErrorPostion = function (exportEntry) {
return ` ${exportEntry.position.fileName} (${exportEntry.position.line},${exportEntry.position.character})`
}
let check_export = function (export_old: any, export_new: any) {
export_old.forEach((currentExport_old) => {
let currentExport_new = export_new.filter((currentExport_new) => {
return currentExport_new.name === currentExport_old.name;
});
if (currentExport_new.length > 1) {
let arrayCall = [];
currentExport_new.forEach((error) => {
arrayCall.push(`${currentErrorPostion(error)}`);
})
add_warning(`Multiple export ${currentExport_new[0].name} times ${currentExport_new.length}`, currentExport_new[0].name, arrayCall);
} else if (currentExport_new.length === 0) {
if (!currentExport_old.skipError) {
add_error(`Not find export ${currentExport_old.name} , old path: [${currentErrorPostion(currentExport_old)}]`, currentExport_old.name);
}
}
});
};
let expandStarExport = function (node: ts.Node): ts.ExportDeclaration {
const ed = node as ts.Node as ts.ExportDeclaration;
const exports = [{ name: "x" }];
const exportSpecifiers = exports.map(e => ts.createExportSpecifier(e.name, e.name));
const exportClause = ts.createNamedExports(exportSpecifiers);
const newEd = ts.updateExportDeclaration(ed, ed.decorators, ed.modifiers, exportClause, ed.moduleSpecifier);
return newEd as ts.ExportDeclaration
};
/** Generate documentation for all classes in a set of .ts files */
function generatExportList(fileNames: string[], options: ts.CompilerOptions): void {
// Build a program using the set of root file names in fileNames
let program = ts.createProgram(fileNames, options);
// Get the checker, we will use it to find more about classes
let checker = program.getTypeChecker();
let exportCurrentVersion: DocEntry[] = [];
// Visit every sourceFile in the program
for (const sourceFile of program.getSourceFiles()) {
if (!sourceFile.isDeclarationFile) {
// Walk the tree to search for classes
ts.forEachChild(sourceFile, visit);
}
}
classList.forEach((classNode) => {
if (classNode.symbol.parent) {
let pathClass = classNode.symbol.parent.escapedName.replace(/"/g, "");
exportedAllPath.forEach((currenPath) => {
let pathNoExtension = currenPath.replace(/\.[^/.]+$/, "");
if (pathNoExtension === pathClass) {
// console.log('pathClass'+ pathClass);
// console.log('pathNoExtension '+ pathNoExtension);
extractExport(classNode);
return;
}
});
}
});
exportCurrentVersion.sort((nameA, nameB) => nameA.name.localeCompare(nameB.name));
console.log(chalk.green('Saving new export in export-new.json'));
fs.writeFileSync('export-new.json', JSON.stringify(exportCurrentVersion, undefined, 4));
try {
var export_old = JSON.parse(fs.readFileSync('export-2.0.0.json', 'utf8'));
} catch (error) {
console.log(chalk.red('export-2.0.0.json not present'));
throw new Error('Undetected export comapring file');
}
var export_new = JSON.parse(JSON.stringify(exportCurrentVersion));
console.log(chalk.green('Comparing export-2.0.0.json and export-new.json'));
check_export(export_old, export_new);
print_warnings();
print_errors();
if (error_array.length > 0) {
throw new Error('Export problems detected');
} else {
return;
}
function extractExport(node: ts.Node) {
//skip file with export-check: exclude comment
if (node.getFullText(node.getSourceFile()).indexOf('export-check: exclude') > 0) {
return;
}
let { line, character } = node.getSourceFile().getLineAndCharacterOfPosition(node.getStart());
//console.log(line + " " + character + " " + node.getSourceFile().fileName);
let symbol = checker.getSymbolAtLocation(node);
if (symbol) {
let arryCalls = recursiveStackSave(node);
let className: any = symbol.escapedName;
let filename = node.getSourceFile().fileName.substring(node.getSourceFile().fileName.indexOf('lib'), node.getSourceFile().fileName.length);
exportCurrentVersion.push(serializeClass(className, line, character, filename, arryCalls));
// if (className === "ContentMetadataService") {
// console.log(chalk.red("exportedAllPath" + exportedAllPath));
// console.log(chalk.red("ContentMetadataService"));
// recursiveStack(node);
// }
} else {
let arryCalls = recursiveStackSave(node);
let className: any = (node as ts.ClassDeclaration).name.escapedText;
let filename = node.getSourceFile().fileName.substring(node.getSourceFile().fileName.indexOf('lib'), node.getSourceFile().fileName.length);
exportCurrentVersion.push(serializeClass(className, line, character, filename, arryCalls));
// if (className === "ContentMetadataService") {
// console.log(chalk.greenBright("exportedAllPath" + exportedAllPath));
// console.log(chalk.greenBright("ContentMetadataService"));
// recursiveStack(node);
// }
}
}
function recursiveStackSave(node: ts.Node, arrayCalls?: string[]) {
if (!arrayCalls) {
arrayCalls = [];
}
let filename = node.getSourceFile().fileName.substring(node.getSourceFile().fileName.indexOf('lib'), node.getSourceFile().fileName.length);
let { line, character } = node.getSourceFile().getLineAndCharacterOfPosition(node.getStart());
arrayCalls.push(node.getSourceFile().fileName);
if (node.parent) {
recursiveStackSave(node.parent, arrayCalls)
}
return arrayCalls;
}
function recursiveStack(node: ts.Node) {
let filename = node.getSourceFile().fileName.substring(node.getSourceFile().fileName.indexOf('lib'), node.getSourceFile().fileName.length);
let { line, character } = node.getSourceFile().getLineAndCharacterOfPosition(node.getStart());
console.log(chalk.bgCyan(line + " " + character + " " + node.getSourceFile().fileName));
if (node.parent) {
recursiveStack(node.parent)
}
}
/** visit nodes finding exported classes */
function visit(node: ts.Node) {
// Only consider exported nodes
if (node.kind === ts.SyntaxKind.ClassDeclaration) {
if (node.decorators) {
node.decorators.forEach((decorator) => {
visit(decorator as ts.Node);
});
}
classList.push(node);
}
if (node.kind === ts.SyntaxKind.PropertyAssignment) {
const initializer = (node as ts.PropertyAssignment).initializer;
visit(initializer as ts.Node);
}
if (node.kind === ts.SyntaxKind.Identifier) {
extractExport(node);
}
if (node.kind === ts.SyntaxKind.ArrayLiteralExpression) {
(node as ts.ArrayLiteralExpression).elements.forEach((element) => {
visit(element as ts.Node);
});
}
if (node.kind === ts.SyntaxKind.Decorator &&
((node as ts.Decorator).expression as any).expression.text === "NgModule") {
((node as ts.Decorator).expression as any).arguments.forEach((argument) => {
argument.properties.forEach((property) => {
if (property.name.escapedText === "exports") {
visit(property as ts.Node);
}
});
});
}
if (ts.isExportDeclaration(node)) {
if (node.exportClause) {
node.exportClause.elements.forEach(exportCurrent => {
extractExport(exportCurrent as ts.Node);
});
} else {
(node.parent as any).resolvedModules.forEach((currentModule) => {
if (currentModule) {
let find;
exportedAllPath.forEach((currentExported) => {
if (currentModule.resolvedFileName === currentExported) {
find = currentExported;
}
})
if (!find) {
exportedAllPath.push(currentModule.resolvedFileName);
}
}
})
visit(node.moduleSpecifier);
}
}
if (ts.isModuleDeclaration(node)) {
// This is a namespace, visit its children
ts.forEachChild(node, visit);
}
}
/** Serialize a symbol into a json object */
function serializeSymbol(className: string, line?: number, character?: number, fileName?: string, arryCalls?: string[]): DocEntry {
return {
position: {
line: line,
character: character,
fileName: fileName
},
name: className
};
}
/** Serialize a class symbol information */
function serializeClass(className: string, line?: number, character?: number, fileName?: string, arryCalls?: string[]) {
let details = serializeSymbol(className, line, character, fileName, arryCalls);
return details;
}
/** True if this is visible outside this file, false otherwise */
function isNodeExported(node: ts.Node): boolean {
return (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Export) !== 0 || (!!node.parent && node.parent.kind === ts.SyntaxKind.SourceFile);
}
}
generatExportList(process.argv.slice(2), {
target: ts.ScriptTarget.ES5, module: ts.ModuleKind.CommonJS, removeComments: false
});

View File

@@ -16,4 +16,4 @@
*/
export * from './components/content-metadata-card/content-metadata-card.component';
export { ContentMetadataModule } from './content-metadata.module';
export * from './services/content-metadata.service';

View File

@@ -70,7 +70,7 @@ export * from './date/date.widget';
export * from './amount/amount.widget';
export * from './dynamic-table/dynamic-table.widget';
export * from './error/error.component';
export { DocumentWidgetComponent } from './document/document.widget';
export * from './document/document.widget';
export * from './date-time/date-time.widget';
// editors (dynamic table)

5171
lib/export-2.0.0.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -32,7 +32,9 @@
"build-content": "ng-packagr -p ./content-services/package.json",
"build-process": "ng-packagr -p ./process-services/package.json",
"build-insights": "ng-packagr -p ./insights/package.json",
"test-export": "node config/test-export.js",
"build-export-check" : "tsc ./config/exportCheck.ts",
"export-check": "node ./config/exportCheck.js ./core/public-api.ts ./process-services/public-api.ts ./content-services/public-api.ts ./insights/public-api.ts",
"test-export": "npm run build-export-check && npm run export-check",
"webpack": "node node_modules/webpack/bin/webpack.js"
},
"main": "./index.js",