add tool to copy method signatures from base classes

This commit is contained in:
Tobias Koppers 2018-07-20 18:16:14 +02:00
parent 9f16238054
commit 0e6d5054aa
5 changed files with 165 additions and 3 deletions

View File

@ -9,6 +9,7 @@
!buildin/*.js
!benchmark/**/*.js
!test/*.js
!tooling/*.js
!test/**/webpack.config.js
!examples/**/webpack.config.js
!schemas/**/*.js

View File

@ -128,10 +128,10 @@
"pretest": "yarn lint",
"prelint": "yarn setup",
"lint": "yarn code-lint && yarn schema-lint && yarn type-lint",
"code-lint": "eslint --cache setup lib bin hot buildin benchmark \"test/*.js\" \"test/**/webpack.config.js\" \"examples/**/webpack.config.js\" \"schemas/**/*.js\"",
"code-lint": "eslint --cache setup lib bin hot buildin benchmark tooling \"test/*.js\" \"test/**/webpack.config.js\" \"examples/**/webpack.config.js\" \"schemas/**/*.js\"",
"type-lint": "tsc --pretty",
"fix": "yarn code-lint --fix",
"pretty": "prettier --write \"setup/**/*.js\" \"lib/**/*.js\" \"bin/*.js\" \"hot/*.js\" \"buildin/*.js\" \"benchmark/**/*.js\" \"test/*.js\" \"test/**/webpack.config.js\" \"examples/**/webpack.config.js\" \"schemas/**/*.js\" \"declarations.d.ts\" \"tsconfig.json\"",
"pretty": "prettier --write \"setup/**/*.js\" \"lib/**/*.js\" \"bin/*.js\" \"hot/*.js\" \"buildin/*.js\" \"benchmark/**/*.js\" \"tooling/*.js\" \"test/*.js\" \"test/**/webpack.config.js\" \"examples/**/webpack.config.js\" \"schemas/**/*.js\" \"declarations.d.ts\" \"tsconfig.json\"",
"schema-lint": "node --max-old-space-size=4096 node_modules/jest-cli/bin/jest --testMatch \"<rootDir>/test/*.lint.js\" --no-verbose",
"benchmark": "node --max-old-space-size=4096 --trace-deprecation node_modules/jest-cli/bin/jest --testMatch \"<rootDir>/test/*.benchmark.js\" --runInBand",
"cover": "yarn cover:init && yarn cover:all && yarn cover:report",

144
tooling/inherit-types.js Normal file
View File

@ -0,0 +1,144 @@
const path = require("path");
const fs = require("fs");
const ts = require("typescript");
const program = require("./typescript-program");
// When --override is set, base jsdoc will override sub class jsdoc
// Elsewise on a conflict it will create a merge conflict in the file
const override = process.argv.includes("--override");
// When --write is set, files will be written in place
// Elsewise it only prints outdated files
const doWrite = process.argv.includes("--write");
const typeChecker = program.getTypeChecker();
/**
* @param {ts.ClassDeclaration} node the class declaration
* @returns {Set<ts.ClassDeclaration>} the base class declarations
*/
const getBaseClasses = node => {
/** @type {Set<ts.ClassDeclaration>} */
const decls = new Set();
if (node.heritageClauses) {
for (const clause of node.heritageClauses) {
for (const clauseType of clause.types) {
const type = typeChecker.getTypeAtLocation(clauseType);
if (ts.isClassDeclaration(type.symbol.valueDeclaration))
decls.add(type.symbol.valueDeclaration);
}
}
}
return decls;
};
/**
* @param {ts.ClassDeclaration} classNode the class declaration
* @param {string} memberName name of the member
* @returns {ts.MethodDeclaration | null} base class member declaration when found
*/
const findDeclarationInBaseClass = (classNode, memberName) => {
for (const baseClass of getBaseClasses(classNode)) {
for (const node of baseClass.members) {
if (ts.isMethodDeclaration(node)) {
if (node.name.getText() === memberName) {
return node;
}
}
}
const result = findDeclarationInBaseClass(baseClass, memberName);
if (result) return result;
}
return null;
};
const libPath = path.resolve(__dirname, "../lib");
for (const sourceFile of program.getSourceFiles()) {
let file = sourceFile.fileName;
if (
file.toLowerCase().startsWith(libPath.replace(/\\/g, "/").toLowerCase())
) {
const updates = [];
sourceFile.forEachChild(node => {
if (ts.isClassDeclaration(node)) {
for (const member of node.members) {
if (ts.isMethodDeclaration(member)) {
const baseDecl = findDeclarationInBaseClass(
node,
member.name.getText()
);
if (baseDecl) {
const memberAsAny = /** @type {any} */ (member);
const baseDeclAsAny = /** @type {any} */ (baseDecl);
const currentJsDoc = memberAsAny.jsDoc && memberAsAny.jsDoc[0];
const baseJsDoc = baseDeclAsAny.jsDoc && baseDeclAsAny.jsDoc[0];
const currentJsDocText = currentJsDoc && currentJsDoc.getText();
let baseJsDocText = baseJsDoc && baseJsDoc.getText();
if (baseJsDocText) {
baseJsDocText = baseJsDocText.replace(
/\t \* @abstract\r?\n/g,
""
);
if (!currentJsDocText) {
// add js doc
updates.push({
member: member.name.getText(),
start: member.getStart(),
end: member.getStart(),
content: baseJsDocText + "\n\t"
});
} else if (
baseJsDocText &&
currentJsDocText !== baseJsDocText
) {
// update js doc
if (override || !doWrite) {
updates.push({
member: member.name.getText(),
start: currentJsDoc.getStart(),
end: currentJsDoc.getEnd(),
content: baseJsDocText
});
} else {
updates.push({
member: member.name.getText(),
start: currentJsDoc.getStart() - 1,
end: currentJsDoc.getEnd(),
content: `<<<<<<< original comment\n\t${currentJsDocText}\n=======\n\t${baseJsDocText}\n>>>>>>> comment from base class`
});
}
}
}
}
}
}
}
});
if (updates.length > 0) {
if (doWrite) {
let fileContent = fs.readFileSync(file, "utf-8");
updates.sort((a, b) => {
return b.start - a.start;
});
for (const update of updates) {
fileContent =
fileContent.substr(0, update.start) +
update.content +
fileContent.substr(update.end);
}
console.log(`${file} ${updates.length} JSDoc comments added/updated`);
fs.writeFileSync(file, fileContent, "utf-8");
} else {
console.log(file);
for (const update of updates) {
console.log(
`* ${update.member} should have this JSDoc:\n\t${update.content}`
);
}
console.log();
}
process.exitCode = 1;
}
}
}

View File

@ -0,0 +1,17 @@
const path = require("path");
const fs = require("fs");
const ts = require("typescript");
const rootPath = path.resolve(__dirname, "..");
const configPath = path.resolve(__dirname, "../tsconfig.json");
const configContent = fs.readFileSync(configPath, "utf-8");
const configJsonFile = ts.parseJsonText(configPath, configContent);
const parsedConfig = ts.parseJsonSourceFileConfigFileContent(
configJsonFile,
ts.sys,
rootPath,
{ noEmit: true }
);
const { fileNames, options } = parsedConfig;
module.exports = ts.createProgram(fileNames, options);

View File

@ -12,5 +12,5 @@
"types": ["node"],
"esModuleInterop": true
},
"include": ["declarations.d.ts", "bin/*.js", "lib/**/*.js"]
"include": ["declarations.d.ts", "bin/*.js", "lib/**/*.js", "tooling/**/*.js"]
}