From 0e6d5054aa01cb58d37f15e63c3fe1f7aebf986f Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Fri, 20 Jul 2018 18:16:14 +0200 Subject: [PATCH 1/2] add tool to copy method signatures from base classes --- .prettierignore | 1 + package.json | 4 +- tooling/inherit-types.js | 144 ++++++++++++++++++++++++++++++++++ tooling/typescript-program.js | 17 ++++ tsconfig.json | 2 +- 5 files changed, 165 insertions(+), 3 deletions(-) create mode 100644 tooling/inherit-types.js create mode 100644 tooling/typescript-program.js diff --git a/.prettierignore b/.prettierignore index 1f362540511..44cf7b81cdd 100644 --- a/.prettierignore +++ b/.prettierignore @@ -9,6 +9,7 @@ !buildin/*.js !benchmark/**/*.js !test/*.js +!tooling/*.js !test/**/webpack.config.js !examples/**/webpack.config.js !schemas/**/*.js diff --git a/package.json b/package.json index dd1fe8b9e7f..3e82d902025 100644 --- a/package.json +++ b/package.json @@ -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 \"/test/*.lint.js\" --no-verbose", "benchmark": "node --max-old-space-size=4096 --trace-deprecation node_modules/jest-cli/bin/jest --testMatch \"/test/*.benchmark.js\" --runInBand", "cover": "yarn cover:init && yarn cover:all && yarn cover:report", diff --git a/tooling/inherit-types.js b/tooling/inherit-types.js new file mode 100644 index 00000000000..79bcdcdd0f7 --- /dev/null +++ b/tooling/inherit-types.js @@ -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} the base class declarations + */ +const getBaseClasses = node => { + /** @type {Set} */ + 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; + } + } +} diff --git a/tooling/typescript-program.js b/tooling/typescript-program.js new file mode 100644 index 00000000000..bd9413ab6ae --- /dev/null +++ b/tooling/typescript-program.js @@ -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); diff --git a/tsconfig.json b/tsconfig.json index b4a8a3be59c..d265178d6c2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -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"] } From 8614c75f1152f9a07d74efdc53b1d0e999c70382 Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Fri, 20 Jul 2018 18:17:51 +0200 Subject: [PATCH 2/2] run inherit types tool on existing code --- lib/DelegatedModule.js | 5 +++++ lib/DependenciesBlock.js | 2 +- lib/DllModule.js | 6 ++++++ lib/ExternalModule.js | 6 ++++++ lib/Generator.js | 14 +++++++++++--- lib/Module.js | 5 +++++ lib/MultiModule.js | 6 ++++++ lib/NormalModule.js | 6 ++++++ lib/RuntimeTemplate.js | 12 ++++++++++++ lib/optimize/ConcatenatedModule.js | 5 +++++ lib/wasm/WebAssemblyGenerator.js | 18 ++++++++++++++++-- lib/wasm/WebAssemblyJavascriptGenerator.js | 18 ++++++++++++++++-- 12 files changed, 95 insertions(+), 8 deletions(-) diff --git a/lib/DelegatedModule.js b/lib/DelegatedModule.js index 09b6c1ebc15..170ceca7a1a 100644 --- a/lib/DelegatedModule.js +++ b/lib/DelegatedModule.js @@ -12,6 +12,7 @@ const DelegatedSourceDependency = require("./dependencies/DelegatedSourceDepende const DelegatedExportsDependency = require("./dependencies/DelegatedExportsDependency"); /** @typedef {import("./dependencies/ModuleDependency")} ModuleDependency */ +/** @typedef {import("./util/createHash").Hash} Hash */ class DelegatedModule extends Module { constructor(sourceRequest, data, type, userRequest, originalRequest) { @@ -99,6 +100,10 @@ class DelegatedModule extends Module { return 42; } + /** + * @param {Hash} hash the hash used to track dependencies + * @returns {void} + */ updateHash(hash) { hash.update(this.type); hash.update(JSON.stringify(this.request)); diff --git a/lib/DependenciesBlock.js b/lib/DependenciesBlock.js index 144884fbbed..142f3eacbaf 100644 --- a/lib/DependenciesBlock.js +++ b/lib/DependenciesBlock.js @@ -73,7 +73,7 @@ class DependenciesBlock { } /** - * @param {Hash} hash the hash used to track dependencies, from "crypto" module + * @param {Hash} hash the hash used to track dependencies * @returns {void} */ updateHash(hash) { diff --git a/lib/DllModule.js b/lib/DllModule.js index c0a45d731ea..0cd9cbcf8ce 100644 --- a/lib/DllModule.js +++ b/lib/DllModule.js @@ -7,6 +7,8 @@ const { RawSource } = require("webpack-sources"); const Module = require("./Module"); +/** @typedef {import("./util/createHash").Hash} Hash */ + class DllModule extends Module { constructor(context, dependencies, name, type) { super("javascript/dynamic", context); @@ -44,6 +46,10 @@ class DllModule extends Module { return 12; } + /** + * @param {Hash} hash the hash used to track dependencies + * @returns {void} + */ updateHash(hash) { hash.update("dll module"); hash.update(this.name || ""); diff --git a/lib/ExternalModule.js b/lib/ExternalModule.js index 98d1560b883..f306710e4fb 100644 --- a/lib/ExternalModule.js +++ b/lib/ExternalModule.js @@ -9,6 +9,8 @@ const Module = require("./Module"); const WebpackMissingModule = require("./dependencies/WebpackMissingModule"); const Template = require("./Template"); +/** @typedef {import("./util/createHash").Hash} Hash */ + class ExternalModule extends Module { constructor(request, type, userRequest) { super("javascript/dynamic", null); @@ -148,6 +150,10 @@ class ExternalModule extends Module { return 42; } + /** + * @param {Hash} hash the hash used to track dependencies + * @returns {void} + */ updateHash(hash) { hash.update(this.externalType); hash.update(JSON.stringify(this.request)); diff --git a/lib/Generator.js b/lib/Generator.js index 5c93506d426..655a9b1da81 100644 --- a/lib/Generator.js +++ b/lib/Generator.js @@ -4,9 +4,10 @@ */ "use strict"; -/** @typedef {import("./Module")} Module */ +/** @typedef {import("./NormalModule")} NormalModule */ /** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */ /** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("./Dependency").DependencyTemplate} DependencyTemplate */ /** * @@ -18,8 +19,8 @@ class Generator { /** * @abstract - * @param {Module} module module for which the code should be generated - * @param {Map} dependencyTemplates mapping from dependencies to templates + * @param {NormalModule} module module for which the code should be generated + * @param {Map} dependencyTemplates mapping from dependencies to templates * @param {RuntimeTemplate} runtimeTemplate the runtime template * @param {string} type which kind of code should be generated * @returns {Source} generated code @@ -35,6 +36,13 @@ class ByTypeGenerator extends Generator { this.map = map; } + /** + * @param {NormalModule} module module for which the code should be generated + * @param {Map} dependencyTemplates mapping from dependencies to templates + * @param {RuntimeTemplate} runtimeTemplate the runtime template + * @param {string} type which kind of code should be generated + * @returns {Source} generated code + */ generate(module, dependencyTemplates, runtimeTemplate, type) { const generator = this.map[type]; if (!generator) { diff --git a/lib/Module.js b/lib/Module.js index 6af4415596d..7685740ab1d 100644 --- a/lib/Module.js +++ b/lib/Module.js @@ -14,6 +14,7 @@ const Template = require("./Template"); /** @typedef {import("./Chunk")} Chunk */ /** @typedef {import("./RequestShortener")} RequestShortener */ /** @typedef {import("./WebpackError")} WebpackError */ +/** @typedef {import("./util/createHash").Hash} Hash */ const EMPTY_RESOLVE_OPTIONS = {}; @@ -298,6 +299,10 @@ class Module extends DependenciesBlock { return true; } + /** + * @param {Hash} hash the hash used to track dependencies + * @returns {void} + */ updateHash(hash) { hash.update(`${this.id}`); hash.update(JSON.stringify(this.usedExports)); diff --git a/lib/MultiModule.js b/lib/MultiModule.js index 6aee82f6694..c8e5d575718 100644 --- a/lib/MultiModule.js +++ b/lib/MultiModule.js @@ -8,6 +8,8 @@ const Module = require("./Module"); const Template = require("./Template"); const { RawSource } = require("webpack-sources"); +/** @typedef {import("./util/createHash").Hash} Hash */ + class MultiModule extends Module { constructor(context, dependencies, name) { super("javascript/dynamic", context); @@ -45,6 +47,10 @@ class MultiModule extends Module { return 16 + this.dependencies.length * 12; } + /** + * @param {Hash} hash the hash used to track dependencies + * @returns {void} + */ updateHash(hash) { hash.update("multi module"); hash.update(this.name || ""); diff --git a/lib/NormalModule.js b/lib/NormalModule.js index 9c5dff5925d..fdabc3000ce 100644 --- a/lib/NormalModule.js +++ b/lib/NormalModule.js @@ -24,6 +24,8 @@ const ModuleWarning = require("./ModuleWarning"); const createHash = require("./util/createHash"); const contextify = require("./util/identifier").contextify; +/** @typedef {import("./util/createHash").Hash} Hash */ + const asString = buf => { if (Buffer.isBuffer(buf)) { return buf.toString("utf-8"); @@ -527,6 +529,10 @@ class NormalModule extends Module { return this._source ? this._source.size() : -1; } + /** + * @param {Hash} hash the hash used to track dependencies + * @returns {void} + */ updateHash(hash) { hash.update(this._buildHash); super.updateHash(hash); diff --git a/lib/RuntimeTemplate.js b/lib/RuntimeTemplate.js index 5f06ed4af8a..613aed1c3ad 100644 --- a/lib/RuntimeTemplate.js +++ b/lib/RuntimeTemplate.js @@ -6,6 +6,8 @@ const Template = require("./Template"); +/** @typedef {import("./Module")} Module */ + module.exports = class RuntimeTemplate { constructor(outputOptions, requestShortener) { this.outputOptions = outputOptions || {}; @@ -188,6 +190,16 @@ module.exports = class RuntimeTemplate { return `${promise || "Promise.resolve()"}.then(${getModuleFunction})`; } + /** + * + * @param {Object} options options object + * @param {boolean=} options.update whether a new variable should be created or the existing one updated + * @param {Module} options.module the module + * @param {string} options.request the request that should be printed as comment + * @param {string} options.importVar name of the import variable + * @param {Module} options.originModule module in which the statement is emitted + * @returns {string} the import statement + */ importStatement({ update, module, request, importVar, originModule }) { if (!module) { return this.missingModuleStatement({ diff --git a/lib/optimize/ConcatenatedModule.js b/lib/optimize/ConcatenatedModule.js index 254ae090f6d..9e8586d2ca9 100644 --- a/lib/optimize/ConcatenatedModule.js +++ b/lib/optimize/ConcatenatedModule.js @@ -21,6 +21,7 @@ const createHash = require("../util/createHash"); /** @typedef {import("../Dependency")} Dependency */ /** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../util/createHash").Hash} Hash */ /** * @typedef {Object} ConcatenationEntry @@ -1180,6 +1181,10 @@ class ConcatenatedModule extends Module { return nameWithNumber; } + /** + * @param {Hash} hash the hash used to track dependencies + * @returns {void} + */ updateHash(hash) { for (const info of this._orderedConcatenationList) { switch (info.type) { diff --git a/lib/wasm/WebAssemblyGenerator.js b/lib/wasm/WebAssemblyGenerator.js index beafd9c465b..2a0395227df 100644 --- a/lib/wasm/WebAssemblyGenerator.js +++ b/lib/wasm/WebAssemblyGenerator.js @@ -21,6 +21,10 @@ const WebAssemblyExportImportedDependency = require("../dependencies/WebAssembly /** @typedef {import("../Module")} Module */ /** @typedef {import("./WebAssemblyUtils").UsedWasmDependency} UsedWasmDependency */ +/** @typedef {import("../NormalModule")} NormalModule */ +/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../Dependency").DependencyTemplate} DependencyTemplate */ /** * @typedef {(ArrayBuffer) => ArrayBuffer} ArrayBufferTransform @@ -366,7 +370,14 @@ class WebAssemblyGenerator extends Generator { this.options = options; } - generate(module) { + /** + * @param {NormalModule} module module for which the code should be generated + * @param {Map} dependencyTemplates mapping from dependencies to templates + * @param {RuntimeTemplate} runtimeTemplate the runtime template + * @param {string} type which kind of code should be generated + * @returns {Source} generated code + */ + generate(module, dependencyTemplates, runtimeTemplate, type) { let bin = module.originalSource().source(); bin = preprocess(bin); @@ -398,7 +409,10 @@ class WebAssemblyGenerator extends Generator { const externalExports = new Set( module.dependencies .filter(d => d instanceof WebAssemblyExportImportedDependency) - .map(d => d.exportName) + .map(d => { + const wasmDep = /** @type {WebAssemblyExportImportedDependency} */ (d); + return wasmDep.exportName; + }) ); /** @type {t.Instruction[]} */ diff --git a/lib/wasm/WebAssemblyJavascriptGenerator.js b/lib/wasm/WebAssemblyJavascriptGenerator.js index 994036d3680..b164a029211 100644 --- a/lib/wasm/WebAssemblyJavascriptGenerator.js +++ b/lib/wasm/WebAssemblyJavascriptGenerator.js @@ -10,8 +10,20 @@ const { RawSource } = require("webpack-sources"); const WebAssemblyImportDependency = require("../dependencies/WebAssemblyImportDependency"); const WebAssemblyExportImportedDependency = require("../dependencies/WebAssemblyExportImportedDependency"); +/** @typedef {import("../NormalModule")} NormalModule */ +/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../Dependency").DependencyTemplate} DependencyTemplate */ + class WebAssemblyJavascriptGenerator extends Generator { - generate(module, dependencyTemplates, runtimeTemplate) { + /** + * @param {NormalModule} module module for which the code should be generated + * @param {Map} dependencyTemplates mapping from dependencies to templates + * @param {RuntimeTemplate} runtimeTemplate the runtime template + * @param {string} type which kind of code should be generated + * @returns {Source} generated code + */ + generate(module, dependencyTemplates, runtimeTemplate, type) { const initIdentifer = Array.isArray(module.usedExports) ? Template.numberToIdentifer(module.usedExports.length) : "__webpack_init__"; @@ -21,6 +33,7 @@ class WebAssemblyJavascriptGenerator extends Generator { const initParams = []; let index = 0; for (const dep of module.dependencies) { + const depAsAny = /** @type {any} */ (dep); if (dep.module) { let importData = importedModules.get(dep.module); if (importData === undefined) { @@ -29,7 +42,8 @@ class WebAssemblyJavascriptGenerator extends Generator { (importData = { importVar: `m${index}`, index, - request: dep.userRequest, + request: + "userRequest" in depAsAny ? depAsAny.userRequest : undefined, names: new Set(), reexports: [] })