Skip to content

Commit 0e6d505

Browse files
committed
add tool to copy method signatures from base classes
1 parent 9f16238 commit 0e6d505

File tree

5 files changed

+165
-3
lines changed

5 files changed

+165
-3
lines changed

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
!buildin/*.js
1010
!benchmark/**/*.js
1111
!test/*.js
12+
!tooling/*.js
1213
!test/**/webpack.config.js
1314
!examples/**/webpack.config.js
1415
!schemas/**/*.js

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,10 +128,10 @@
128128
"pretest": "yarn lint",
129129
"prelint": "yarn setup",
130130
"lint": "yarn code-lint && yarn schema-lint && yarn type-lint",
131-
"code-lint": "eslint --cache setup lib bin hot buildin benchmark \"test/*.js\" \"test/**/webpack.config.js\" \"examples/**/webpack.config.js\" \"schemas/**/*.js\"",
131+
"code-lint": "eslint --cache setup lib bin hot buildin benchmark tooling \"test/*.js\" \"test/**/webpack.config.js\" \"examples/**/webpack.config.js\" \"schemas/**/*.js\"",
132132
"type-lint": "tsc --pretty",
133133
"fix": "yarn code-lint --fix",
134-
"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\"",
134+
"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\"",
135135
"schema-lint": "node --max-old-space-size=4096 node_modules/jest-cli/bin/jest --testMatch \"<rootDir>/test/*.lint.js\" --no-verbose",
136136
"benchmark": "node --max-old-space-size=4096 --trace-deprecation node_modules/jest-cli/bin/jest --testMatch \"<rootDir>/test/*.benchmark.js\" --runInBand",
137137
"cover": "yarn cover:init && yarn cover:all && yarn cover:report",

tooling/inherit-types.js

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
const path = require("path");
2+
const fs = require("fs");
3+
const ts = require("typescript");
4+
const program = require("./typescript-program");
5+
6+
// When --override is set, base jsdoc will override sub class jsdoc
7+
// Elsewise on a conflict it will create a merge conflict in the file
8+
const override = process.argv.includes("--override");
9+
10+
// When --write is set, files will be written in place
11+
// Elsewise it only prints outdated files
12+
const doWrite = process.argv.includes("--write");
13+
14+
const typeChecker = program.getTypeChecker();
15+
16+
/**
17+
* @param {ts.ClassDeclaration} node the class declaration
18+
* @returns {Set<ts.ClassDeclaration>} the base class declarations
19+
*/
20+
const getBaseClasses = node => {
21+
/** @type {Set<ts.ClassDeclaration>} */
22+
const decls = new Set();
23+
if (node.heritageClauses) {
24+
for (const clause of node.heritageClauses) {
25+
for (const clauseType of clause.types) {
26+
const type = typeChecker.getTypeAtLocation(clauseType);
27+
if (ts.isClassDeclaration(type.symbol.valueDeclaration))
28+
decls.add(type.symbol.valueDeclaration);
29+
}
30+
}
31+
}
32+
return decls;
33+
};
34+
35+
/**
36+
* @param {ts.ClassDeclaration} classNode the class declaration
37+
* @param {string} memberName name of the member
38+
* @returns {ts.MethodDeclaration | null} base class member declaration when found
39+
*/
40+
const findDeclarationInBaseClass = (classNode, memberName) => {
41+
for (const baseClass of getBaseClasses(classNode)) {
42+
for (const node of baseClass.members) {
43+
if (ts.isMethodDeclaration(node)) {
44+
if (node.name.getText() === memberName) {
45+
return node;
46+
}
47+
}
48+
}
49+
const result = findDeclarationInBaseClass(baseClass, memberName);
50+
if (result) return result;
51+
}
52+
return null;
53+
};
54+
55+
const libPath = path.resolve(__dirname, "../lib");
56+
57+
for (const sourceFile of program.getSourceFiles()) {
58+
let file = sourceFile.fileName;
59+
if (
60+
file.toLowerCase().startsWith(libPath.replace(/\\/g, "/").toLowerCase())
61+
) {
62+
const updates = [];
63+
sourceFile.forEachChild(node => {
64+
if (ts.isClassDeclaration(node)) {
65+
for (const member of node.members) {
66+
if (ts.isMethodDeclaration(member)) {
67+
const baseDecl = findDeclarationInBaseClass(
68+
node,
69+
member.name.getText()
70+
);
71+
if (baseDecl) {
72+
const memberAsAny = /** @type {any} */ (member);
73+
const baseDeclAsAny = /** @type {any} */ (baseDecl);
74+
const currentJsDoc = memberAsAny.jsDoc && memberAsAny.jsDoc[0];
75+
const baseJsDoc = baseDeclAsAny.jsDoc && baseDeclAsAny.jsDoc[0];
76+
const currentJsDocText = currentJsDoc && currentJsDoc.getText();
77+
let baseJsDocText = baseJsDoc && baseJsDoc.getText();
78+
if (baseJsDocText) {
79+
baseJsDocText = baseJsDocText.replace(
80+
/\t \* @abstract\r?\n/g,
81+
""
82+
);
83+
if (!currentJsDocText) {
84+
// add js doc
85+
updates.push({
86+
member: member.name.getText(),
87+
start: member.getStart(),
88+
end: member.getStart(),
89+
content: baseJsDocText + "\n\t"
90+
});
91+
} else if (
92+
baseJsDocText &&
93+
currentJsDocText !== baseJsDocText
94+
) {
95+
// update js doc
96+
if (override || !doWrite) {
97+
updates.push({
98+
member: member.name.getText(),
99+
start: currentJsDoc.getStart(),
100+
end: currentJsDoc.getEnd(),
101+
content: baseJsDocText
102+
});
103+
} else {
104+
updates.push({
105+
member: member.name.getText(),
106+
start: currentJsDoc.getStart() - 1,
107+
end: currentJsDoc.getEnd(),
108+
content: `<<<<<<< original comment\n\t${currentJsDocText}\n=======\n\t${baseJsDocText}\n>>>>>>> comment from base class`
109+
});
110+
}
111+
}
112+
}
113+
}
114+
}
115+
}
116+
}
117+
});
118+
if (updates.length > 0) {
119+
if (doWrite) {
120+
let fileContent = fs.readFileSync(file, "utf-8");
121+
updates.sort((a, b) => {
122+
return b.start - a.start;
123+
});
124+
for (const update of updates) {
125+
fileContent =
126+
fileContent.substr(0, update.start) +
127+
update.content +
128+
fileContent.substr(update.end);
129+
}
130+
console.log(`${file} ${updates.length} JSDoc comments added/updated`);
131+
fs.writeFileSync(file, fileContent, "utf-8");
132+
} else {
133+
console.log(file);
134+
for (const update of updates) {
135+
console.log(
136+
`* ${update.member} should have this JSDoc:\n\t${update.content}`
137+
);
138+
}
139+
console.log();
140+
}
141+
process.exitCode = 1;
142+
}
143+
}
144+
}

tooling/typescript-program.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
const path = require("path");
2+
const fs = require("fs");
3+
const ts = require("typescript");
4+
5+
const rootPath = path.resolve(__dirname, "..");
6+
const configPath = path.resolve(__dirname, "../tsconfig.json");
7+
const configContent = fs.readFileSync(configPath, "utf-8");
8+
const configJsonFile = ts.parseJsonText(configPath, configContent);
9+
const parsedConfig = ts.parseJsonSourceFileConfigFileContent(
10+
configJsonFile,
11+
ts.sys,
12+
rootPath,
13+
{ noEmit: true }
14+
);
15+
const { fileNames, options } = parsedConfig;
16+
17+
module.exports = ts.createProgram(fileNames, options);

tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@
1212
"types": ["node"],
1313
"esModuleInterop": true
1414
},
15-
"include": ["declarations.d.ts", "bin/*.js", "lib/**/*.js"]
15+
"include": ["declarations.d.ts", "bin/*.js", "lib/**/*.js", "tooling/**/*.js"]
1616
}

0 commit comments

Comments
 (0)