├── .gitignore ├── .gitattributes ├── .prettierrc.js ├── .editorconfig ├── declarations.d.ts ├── lib ├── typescript-program.js ├── process-schema.js └── argv.js ├── tsconfig.json ├── .github └── workflows │ └── test.yml ├── lockfile-lint └── index.js ├── LICENSE ├── package.json ├── type-coverage └── index.js ├── inherit-types └── index.js ├── format-schemas └── index.js ├── README.md ├── schemas-lint └── index.js ├── precompile-schemas └── index.js ├── format-file-header └── index.js ├── compile-to-definitions └── index.js ├── yarn.lock └── generate-types └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 80, 3 | useTabs: true, 4 | tabWidth: 2, 5 | overrides: [ 6 | { 7 | files: "*.json", 8 | options: { 9 | useTabs: false, 10 | }, 11 | }, 12 | ], 13 | }; 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | max_line_length = 80 10 | 11 | [*.{yml,yaml,json}] 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | 18 | [*.snap] 19 | trim_trailing_whitespace = false 20 | -------------------------------------------------------------------------------- /declarations.d.ts: -------------------------------------------------------------------------------- 1 | import * as ts from "typescript"; 2 | declare module "typescript" { 3 | export function getDeclarationModifierFlagsFromSymbol( 4 | s: ts.Symbol, 5 | ): ts.ModifierFlags; 6 | export function signatureHasRestParameter(signature: ts.Signature): boolean; 7 | interface Symbol { 8 | type?: ts.Type; 9 | parent: Symbol | undefined; 10 | } 11 | interface Declaration { 12 | expression?: ts.Expression; 13 | } 14 | interface Signature { 15 | thisParameter?: Symbol; 16 | } 17 | interface Type { 18 | intrinsicName?: string; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/typescript-program.js: -------------------------------------------------------------------------------- 1 | const { root } = require("./argv"); 2 | 3 | const path = require("path"); 4 | const fs = require("fs"); 5 | const ts = require("typescript"); 6 | 7 | const configPath = path.resolve(root, "tsconfig.json"); 8 | const configContent = fs.readFileSync(configPath, "utf-8"); 9 | const configJsonFile = ts.parseJsonText(configPath, configContent); 10 | const parsedConfig = ts.parseJsonSourceFileConfigFileContent( 11 | configJsonFile, 12 | ts.sys, 13 | root, 14 | { noEmit: true }, 15 | ); 16 | const { fileNames, options } = parsedConfig; 17 | 18 | module.exports = ts.createProgram(fileNames, options); 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "module": "commonjs", 5 | "lib": ["es2017", "dom"], 6 | "allowJs": true, 7 | "checkJs": true, 8 | "noEmit": true, 9 | "strict": false, 10 | "noImplicitThis": true, 11 | "alwaysStrict": true, 12 | "types": ["node"], 13 | "esModuleInterop": true 14 | }, 15 | "include": [ 16 | "declarations.d.ts", 17 | "lib/**/*.js", 18 | "compile-to-defintions/**/*.js", 19 | "format-file-header/**/*.js", 20 | "format-schemas/**/*.js", 21 | "generate-types/**/*.js", 22 | "inherit-types/**/*.js", 23 | "lockfile-lint/**/*.js", 24 | "schemas-lint/**/*.js", 25 | "type-coverage/**/*.js" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | test: 11 | strategy: 12 | fail-fast: false 13 | 14 | matrix: 15 | os: [ubuntu-latest, windows-latest, macos-latest] 16 | node-version: [16.x, 18.x] 17 | 18 | runs-on: ${{ matrix.os }} 19 | 20 | steps: 21 | - name: Setup Git 22 | if: matrix.os == 'windows-latest' 23 | run: git config --global core.autocrlf input 24 | 25 | - uses: actions/checkout@v4 26 | 27 | - name: Use Node.js ${{ matrix.node-version }} 28 | uses: actions/setup-node@v4 29 | with: 30 | node-version: ${{ matrix.node-version }} 31 | cache: yarn 32 | 33 | - run: yarn --frozen-lockfile 34 | 35 | - run: yarn test 36 | -------------------------------------------------------------------------------- /lockfile-lint/index.js: -------------------------------------------------------------------------------- 1 | const { root } = require("../lib/argv"); 2 | 3 | const fs = require("fs"); 4 | const path = require("path"); 5 | const lockfile = require("@yarnpkg/lockfile"); 6 | 7 | const file = fs.readFileSync(path.resolve(root, "yarn.lock"), "utf-8"); 8 | const result = lockfile.parse(file); 9 | 10 | if (result.type !== "success") { 11 | console.log("Unable to parse lockfile"); 12 | process.exitCode = 1; 13 | } else { 14 | const content = result.object; 15 | for (const dep of Object.keys(content)) { 16 | if (/^tooling@/.test(dep)) continue; 17 | const info = content[dep]; 18 | if (!/^https:\/\/registry\.yarnpkg\.com\//.test(info.resolved)) { 19 | console.log(`${dep} should resolve to an npm package`); 20 | process.exitCode = 1; 21 | } 22 | if (!/^(sha1|sha512)-/.test(info.integrity)) { 23 | console.log(`${dep} should have an integrity hash`); 24 | process.exitCode = 1; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 webpack 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tooling", 3 | "version": "1.24.3", 4 | "description": "A collection of reusable tooling for webpack repos.", 5 | "main": "index.js", 6 | "repository": "https://github.com/webpack/tooling", 7 | "author": "Tobias Koppers ", 8 | "license": "MIT", 9 | "private": true, 10 | "scripts": { 11 | "pretty-lint-base": "node node_modules/prettier/bin/prettier.cjs --cache --ignore-unknown .", 12 | "pretty-lint-fix": "yarn pretty-lint-base --log-level warn --write", 13 | "pretty-lint": "yarn pretty-lint-base --check", 14 | "test": "yarn lint", 15 | "lint": "yarn special-lint && yarn pretty-lint && tsc", 16 | "special-lint": "node lockfile-lint" 17 | }, 18 | "peerDependencies": { 19 | "prettier": "*", 20 | "typescript": "*" 21 | }, 22 | "devDependencies": { 23 | "@types/node": "^22.5.4", 24 | "@types/yargs": "^15.0.12", 25 | "prettier": "^3.0.1", 26 | "typescript": "^5.6.2" 27 | }, 28 | "dependencies": { 29 | "@yarnpkg/lockfile": "^1.1.0", 30 | "ajv": "^8.1.0", 31 | "commondir": "^1.0.1", 32 | "glob": "^7.1.6", 33 | "json-schema-to-typescript": "^9.1.1", 34 | "terser": "^5.32.0", 35 | "yargs": "^16.1.1" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/process-schema.js: -------------------------------------------------------------------------------- 1 | const NESTED_WITH_NAME = ["definitions", "properties"]; 2 | 3 | const NESTED_DIRECT = ["items", "additionalProperties", "not"]; 4 | 5 | const NESTED_ARRAY = ["oneOf", "anyOf", "allOf"]; 6 | 7 | const processSchema = (visitor, json, context) => { 8 | json = { ...json }; 9 | if (visitor.schema) json = visitor.schema(json, context); 10 | 11 | for (const name of NESTED_WITH_NAME) { 12 | if (name in json && json[name] && typeof json[name] === "object") { 13 | if (visitor.object) json[name] = visitor.object(json[name], context); 14 | for (const key in json[name]) { 15 | json[name][key] = processSchema(visitor, json[name][key], context); 16 | } 17 | } 18 | } 19 | for (const name of NESTED_DIRECT) { 20 | if (name in json && json[name] && typeof json[name] === "object") { 21 | json[name] = processSchema(visitor, json[name], context); 22 | } 23 | } 24 | for (const name of NESTED_ARRAY) { 25 | if (name in json && Array.isArray(json[name])) { 26 | json[name] = json[name].slice(); 27 | for (let i = 0; i < json[name].length; i++) { 28 | json[name][i] = processSchema(visitor, json[name][i], context); 29 | } 30 | if (visitor.array) visitor.array(json[name], context); 31 | } 32 | } 33 | 34 | return json; 35 | }; 36 | module.exports = processSchema; 37 | -------------------------------------------------------------------------------- /lib/argv.js: -------------------------------------------------------------------------------- 1 | module.exports = require("yargs") 2 | .boolean("write") 3 | .describe( 4 | "write", 5 | "Write updated files to disk, otherwise it only checks if they are correct.", 6 | ) 7 | 8 | .boolean("verbose") 9 | .describe("verbose", "Print more info to output.") 10 | 11 | .string("root") 12 | .describe( 13 | "root", 14 | "Root repository directory (optional if calling from package.json scripts).", 15 | ) 16 | .default( 17 | "root", 18 | process.env.INIT_CWD || process.cwd(), 19 | "The root directory from calling package.json or the current directory", 20 | ) 21 | 22 | .string("schemas") 23 | .describe("schemas", "Glob to find schemas in root directory.") 24 | .default("schemas", "./schemas/**/*.json") 25 | 26 | .string("declarations") 27 | .describe( 28 | "declarations", 29 | "Output folder for declarations generated from schemas.", 30 | ) 31 | .default("declarations", "declarations") 32 | 33 | .string("source") 34 | .describe("source", "Glob to find source code in root directory.") 35 | .default("source", "./lib/**/*.js") 36 | 37 | .string("types") 38 | .describe("types", "Output file for types declarations.") 39 | .default("types", "types.d.ts") 40 | 41 | .boolean("templateLiterals") 42 | .default("templateLiterals", true) 43 | .describe("templateLiterals", "Allow template literal types.") 44 | 45 | .parse(); 46 | -------------------------------------------------------------------------------- /type-coverage/index.js: -------------------------------------------------------------------------------- 1 | // loosely based on https://github.com/plantain-00/type-coverage 2 | 3 | const { root } = require("../lib/argv"); 4 | 5 | const path = require("path"); 6 | const fs = require("fs"); 7 | const ts = require("typescript"); 8 | const program = require("../lib/typescript-program"); 9 | 10 | const typeChecker = program.getTypeChecker(); 11 | 12 | /** 13 | * @typedef {Object} Location 14 | * @property {number} line 15 | * @property {number} column 16 | */ 17 | 18 | /** 19 | * @typedef {Object} FileReport 20 | * @property {string} path 21 | * @property {Record} statementMap 22 | * @property {{}} fnMap 23 | * @property {{}} branchMap 24 | * @property {Record} s 25 | * @property {{}} f 26 | * @property {{}} b 27 | */ 28 | 29 | /** @type {Record} */ 30 | const coverageReport = Object.create(null); 31 | 32 | for (const sourceFile of program.getSourceFiles()) { 33 | let file = sourceFile.fileName; 34 | if (!sourceFile.isDeclarationFile) { 35 | /** @type {FileReport} */ 36 | const rep = { 37 | path: path.sep !== "/" ? file.replace(/\//g, path.sep) : file, 38 | statementMap: {}, 39 | fnMap: {}, 40 | branchMap: {}, 41 | s: {}, 42 | f: {}, 43 | b: {}, 44 | }; 45 | coverageReport[rep.path] = rep; 46 | let statementIndex = 0; 47 | 48 | /** 49 | * @param {ts.Node} node the node to be walked 50 | * @returns {void} 51 | */ 52 | const walkNode = (node) => { 53 | if (ts.isIdentifier(node) || node.kind === ts.SyntaxKind.ThisKeyword) { 54 | const type = typeChecker.getTypeAtLocation(node); 55 | if (type) { 56 | const { line, character } = ts.getLineAndCharacterOfPosition( 57 | sourceFile, 58 | node.getStart(), 59 | ); 60 | const { line: lineEnd, character: characterEnd } = 61 | ts.getLineAndCharacterOfPosition(sourceFile, node.getEnd()); 62 | const typeText = typeChecker.typeToString(type); 63 | let isExternal = false; 64 | 65 | /** 66 | * @param {ts.Type} type the type to be checked 67 | * @returns {void} 68 | */ 69 | const checkDecls = (type) => { 70 | if (!type.symbol) return; 71 | for (const decl of type.symbol.getDeclarations()) { 72 | const sourceFile = decl.getSourceFile(); 73 | if (sourceFile && sourceFile.isDeclarationFile) isExternal = true; 74 | } 75 | }; 76 | if (node.parent && ts.isPropertyAccessExpression(node.parent)) { 77 | const expressionType = typeChecker.getTypeAtLocation( 78 | node.parent.expression, 79 | ); 80 | checkDecls(expressionType); 81 | } 82 | if (/^(<.*>)?\(/.test(typeText)) { 83 | checkDecls(type); 84 | } 85 | const isTyped = 86 | isExternal || 87 | (!(type.flags & ts.TypeFlags.Any) && !/\bany\b/.test(typeText)); 88 | rep.statementMap[statementIndex] = { 89 | start: { 90 | line: line + 1, 91 | column: character, 92 | }, 93 | end: { 94 | line: lineEnd + 1, 95 | column: characterEnd - 1, 96 | }, 97 | }; 98 | rep.s[statementIndex] = isTyped ? typeText.length : 0; 99 | statementIndex++; 100 | } 101 | } 102 | node.forEachChild(walkNode); 103 | }; 104 | sourceFile.forEachChild(walkNode); 105 | } 106 | } 107 | 108 | const outputDirectory = path.resolve(root, "coverage"); 109 | fs.mkdirSync(outputDirectory, { recursive: true }); 110 | fs.writeFileSync( 111 | path.resolve(outputDirectory, "coverage-types.json"), 112 | JSON.stringify(coverageReport), 113 | "utf-8", 114 | ); 115 | -------------------------------------------------------------------------------- /inherit-types/index.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const fs = require("fs"); 3 | const ts = require("typescript"); 4 | const program = require("../lib/typescript-program"); 5 | 6 | // When --write is set, files will be written in place 7 | // Otherwise it only prints outdated files 8 | const doWrite = process.argv.includes("--write"); 9 | 10 | const typeChecker = program.getTypeChecker(); 11 | 12 | /** 13 | * @param {ts.MethodDeclaration} decl 14 | * @returns {boolean} true if the method is static 15 | */ 16 | const isStaticMethod = (decl) => { 17 | return !!(ts.getCombinedModifierFlags(decl) & ts.ModifierFlags.Static); 18 | }; 19 | 20 | /** 21 | * @param {ts.ClassDeclaration | ts.ClassExpression} node the class declaration 22 | * @returns {Set} the base class declarations 23 | */ 24 | const getBaseClasses = (node) => { 25 | try { 26 | /** @type {Set} */ 27 | const decls = new Set(); 28 | if (node.heritageClauses) { 29 | for (const clause of node.heritageClauses) { 30 | for (const clauseType of clause.types) { 31 | const type = typeChecker.getTypeAtLocation(clauseType); 32 | if (type.symbol) { 33 | const decl = type.symbol.valueDeclaration; 34 | if (ts.isClassDeclaration(decl) || ts.isClassExpression(decl)) 35 | decls.add(decl); 36 | } 37 | } 38 | } 39 | } 40 | return decls; 41 | } catch (e) { 42 | e.message += ` while getting the base class of ${node.name}`; 43 | throw e; 44 | } 45 | }; 46 | 47 | /** 48 | * @param {ts.ClassDeclaration | ts.ClassExpression} classNode the class declaration 49 | * @param {string} memberName name of the member 50 | * @returns {ts.MethodDeclaration | null} base class member declaration when found 51 | */ 52 | const findDeclarationInBaseClass = (classNode, memberName) => { 53 | for (const baseClass of getBaseClasses(classNode)) { 54 | for (const node of baseClass.members) { 55 | if (ts.isMethodDeclaration(node) && !isStaticMethod(node)) { 56 | if (node.name.getText() === memberName) { 57 | return node; 58 | } 59 | } 60 | } 61 | const result = findDeclarationInBaseClass(baseClass, memberName); 62 | if (result) return result; 63 | } 64 | return null; 65 | }; 66 | 67 | for (const sourceFile of program.getSourceFiles()) { 68 | let file = sourceFile.fileName; 69 | if (!sourceFile.isDeclarationFile) { 70 | const updates = []; 71 | 72 | /** 73 | * @param {ts.Node} node the traversed node 74 | * @returns {void} 75 | */ 76 | const nodeHandler = (node) => { 77 | if (ts.isClassDeclaration(node) || ts.isClassExpression(node)) { 78 | for (const member of node.members) { 79 | if (ts.isMethodDeclaration(member) && !isStaticMethod(member)) { 80 | const baseDecl = findDeclarationInBaseClass( 81 | node, 82 | member.name.getText(), 83 | ); 84 | if (baseDecl) { 85 | const memberAsAny = /** @type {any} */ (member); 86 | const baseDeclAsAny = /** @type {any} */ (baseDecl); 87 | const currentJsDoc = memberAsAny.jsDoc && memberAsAny.jsDoc[0]; 88 | const baseJsDoc = baseDeclAsAny.jsDoc && baseDeclAsAny.jsDoc[0]; 89 | const currentJsDocText = 90 | currentJsDoc && currentJsDoc.getText().replace(/\r\n?/g, "\n"); 91 | let baseJsDocText = 92 | baseJsDoc && baseJsDoc.getText().replace(/\r\n?/g, "\n"); 93 | if (baseJsDocText) { 94 | baseJsDocText = baseJsDocText.replace( 95 | /\t \* @abstract\r?\n/g, 96 | "", 97 | ); 98 | if (!currentJsDocText) { 99 | // add js doc 100 | updates.push({ 101 | member: member.name.getText(), 102 | start: member.getStart(), 103 | end: member.getStart(), 104 | content: baseJsDocText + "\n\t", 105 | oldContent: "", 106 | }); 107 | } else if ( 108 | baseJsDocText && 109 | currentJsDocText !== baseJsDocText 110 | ) { 111 | // update js doc 112 | updates.push({ 113 | member: member.name.getText(), 114 | start: currentJsDoc.getStart(), 115 | end: currentJsDoc.getEnd(), 116 | content: baseJsDocText, 117 | oldContent: currentJsDocText, 118 | }); 119 | } 120 | } 121 | } 122 | } 123 | } 124 | } else { 125 | node.forEachChild(nodeHandler); 126 | } 127 | }; 128 | try { 129 | sourceFile.forEachChild(nodeHandler); 130 | } catch (e) { 131 | e.message += ` while processing ${file}`; 132 | throw e; 133 | } 134 | 135 | if (updates.length > 0) { 136 | if (doWrite) { 137 | let fileContent = fs.readFileSync(file, "utf-8"); 138 | updates.sort((a, b) => { 139 | return b.start - a.start; 140 | }); 141 | for (const update of updates) { 142 | fileContent = 143 | fileContent.slice(0, update.start) + 144 | update.content + 145 | fileContent.slice(update.end); 146 | } 147 | console.log(`${file} ${updates.length} JSDoc comments added/updated`); 148 | fs.writeFileSync(file, fileContent, "utf-8"); 149 | } else { 150 | console.log(file); 151 | for (const update of updates) { 152 | console.log( 153 | `* ${update.member} should have this JSDoc:\n\t${update.content}`, 154 | ); 155 | if (update.oldContent) { 156 | console.log(`instead of\n\t${update.oldContent}`); 157 | } 158 | } 159 | console.log(); 160 | process.exitCode = 1; 161 | } 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /format-schemas/index.js: -------------------------------------------------------------------------------- 1 | const argv = require("../lib/argv"); 2 | 3 | const { write: doWrite, root, schemas: schemasGlob } = argv; 4 | 5 | const fs = require("fs"); 6 | const glob = require("glob"); 7 | const prettier = require("prettier"); 8 | const processSchema = require("../lib/process-schema"); 9 | 10 | const schemas = glob.sync(schemasGlob, { cwd: root, absolute: true }); 11 | const minSlashes = schemas 12 | .map((p) => p.split(/[\\/]/).length) 13 | .reduce((a, b) => Math.min(a, b), Infinity); 14 | const baseSchemaPaths = schemas.filter( 15 | (p) => p.split(/[\\/]/).length === minSlashes, 16 | ); 17 | const baseDefinitions = new Map(); 18 | for (const baseSchemaPath of baseSchemaPaths) { 19 | for (const [name, schema] of Object.entries( 20 | require(baseSchemaPath).definitions, 21 | )) { 22 | baseDefinitions.set(name, schema); 23 | } 24 | } 25 | 26 | const sortObjectAlphabetically = (obj) => { 27 | const keys = Object.keys(obj).sort(); 28 | const newObj = {}; 29 | for (const key of keys) { 30 | newObj[key] = obj[key]; 31 | } 32 | return newObj; 33 | }; 34 | 35 | const typeOrder = [ 36 | "array", 37 | "enum", 38 | "RegExp", 39 | "number", 40 | "boolean", 41 | "string", 42 | "object", 43 | "Function", 44 | undefined, 45 | ]; 46 | 47 | const sortArrayByType = (array) => { 48 | array.sort((a, b) => { 49 | const aType = a.type || a.instanceof || (a.enum && "enum"); 50 | const bType = b.type || b.instanceof || (b.enum && "enum"); 51 | const aPos = typeOrder.indexOf(aType); 52 | const bPos = typeOrder.indexOf(bType); 53 | if (aPos === bPos) { 54 | return array.indexOf(a) - array.indexOf(b); 55 | } 56 | return aPos - bPos; 57 | }); 58 | }; 59 | 60 | const sortObjectWithList = (obj, props) => { 61 | const keys = Object.keys(obj) 62 | .filter((p) => !props.includes(p)) 63 | .sort(); 64 | const newObj = {}; 65 | for (const key of props) { 66 | if (key in obj) { 67 | newObj[key] = obj[key]; 68 | } 69 | } 70 | for (const key of keys) { 71 | newObj[key] = obj[key]; 72 | } 73 | return newObj; 74 | }; 75 | 76 | const PROPERTIES = [ 77 | "$ref", 78 | "definitions", 79 | 80 | "$id", 81 | "id", 82 | "title", 83 | "description", 84 | "type", 85 | 86 | "cli", 87 | 88 | "items", 89 | "minItems", 90 | "uniqueItems", 91 | 92 | "implements", 93 | "additionalProperties", 94 | "properties", 95 | "required", 96 | "minProperties", 97 | 98 | "oneOf", 99 | "anyOf", 100 | "allOf", 101 | "enum", 102 | 103 | "absolutePath", 104 | 105 | "undefinedAsNull", 106 | 107 | "minLength", 108 | 109 | "minimum", 110 | 111 | "instanceof", 112 | 113 | "tsType", 114 | ]; 115 | 116 | const processJson = processSchema.bind(null, { 117 | schema: (json, context) => { 118 | json = sortObjectWithList(json, PROPERTIES); 119 | 120 | if (json.definitions) { 121 | json.definitions = { ...json.definitions }; 122 | for (const key of Object.keys(json.definitions)) { 123 | let baseDef = baseDefinitions.get(key); 124 | if (baseDef) { 125 | baseDef = processSchema( 126 | { 127 | schema: (json) => { 128 | const tsType = json.tsType; 129 | if (tsType) { 130 | json = { 131 | ...json, 132 | tsType: tsType.replace( 133 | /\.\.\//g, 134 | context.importPrefix + "../", 135 | ), 136 | }; 137 | } 138 | return json; 139 | }, 140 | }, 141 | baseDef, 142 | {}, 143 | ); 144 | json.definitions[key] = baseDef; 145 | } 146 | } 147 | } 148 | 149 | if (json.implements) { 150 | const prefix = "#/definitions/"; 151 | for (const impl of [].concat(json.implements)) { 152 | if (!impl.startsWith(prefix)) { 153 | console.warn( 154 | `"implements": "${impl}" -> should start with "${prefix}"`, 155 | ); 156 | continue; 157 | } 158 | const name = impl.slice(prefix.length); 159 | const referencedSchema = context.definitions[name]; 160 | if (!referencedSchema) { 161 | console.warn( 162 | `"implements": "${impl}" -> referenced schema not found`, 163 | ); 164 | continue; 165 | } 166 | if (typeof referencedSchema.properties !== "object") { 167 | console.warn( 168 | `"implements": "${impl}" -> referenced schema has no properties`, 169 | ); 170 | continue; 171 | } 172 | json.properties = { 173 | ...json.properties, 174 | ...referencedSchema.properties, 175 | }; 176 | } 177 | } 178 | 179 | return json; 180 | }, 181 | array: (json, context) => { 182 | return sortArrayByType(json); 183 | }, 184 | object: (json, context) => { 185 | return sortObjectAlphabetically(json); 186 | }, 187 | }); 188 | 189 | const formatSchema = async (schemaPath) => { 190 | const json = require(schemaPath); 191 | const processedJson = processJson(json, { 192 | schema: json, 193 | definitions: json.definitions, 194 | importPrefix: "../".repeat(schemaPath.split(/[\\/]/).length - minSlashes), 195 | }); 196 | const rawString = JSON.stringify(processedJson, null, 2); 197 | const config = await prettier.resolveConfig(schemaPath); 198 | const prettyString = await prettier.format(rawString, config); 199 | let normalizedContent = ""; 200 | try { 201 | const content = fs.readFileSync(schemaPath, "utf-8"); 202 | normalizedContent = content.replace(/\r\n?/g, "\n"); 203 | } catch (e) { 204 | // ignore 205 | } 206 | if (normalizedContent.trim() !== prettyString.trim()) { 207 | if (doWrite) { 208 | fs.writeFileSync(schemaPath, prettyString, "utf-8"); 209 | console.error(`${schemaPath} updated`); 210 | } else { 211 | console.error(`${schemaPath} need to be updated`); 212 | process.exitCode = 1; 213 | } 214 | } 215 | }; 216 | 217 | for (let absPath of schemas) { 218 | formatSchema(absPath); 219 | } 220 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tooling 2 | 3 | A collection of reusable tooling for webpack repos 4 | 5 | ## Setup 6 | 7 | Add this repo as dev dependency: 8 | 9 | ```json 10 | "devDependencies": { 11 | "tooling": "webpack/tooling" 12 | } 13 | ``` 14 | 15 | The lockfile will take care pinning the version. 16 | Run `yarn upgrade tooling` to upgrade the to latest version. 17 | 18 | Add two scripts to the package.json: 19 | 20 | ```json 21 | "scripts": { 22 | "lint:special": "...", 23 | "fix:special": "..." 24 | } 25 | ``` 26 | 27 | Add all tooling that should be used to these scripts (concatenated with `&&`). In the `fix:special` version pass `--write` to all tooling. 28 | 29 | Example: 30 | 31 | ```json 32 | "scripts": { 33 | "lint:special": "node node_modules/tooling/compile-to-definitions", 34 | "fix:special": "node node_modules/tooling/compile-to-definitions --write" 35 | } 36 | ``` 37 | 38 | ## Common arguments 39 | 40 | By default all tooling checks if affected files are up-to-date, so it can be added as linting step in CI. 41 | Use `--write` to switch to updating mode. 42 | In this mode affected files are updated to the fixed version when possible. 43 | 44 | By default all tooling will operate in the root directory of the calling project. 45 | Use `--root some/dir` to operate in a different directory. 46 | 47 | Some tooling displays more information when `--verbose` is used. 48 | 49 | Most tooling uses prettier to format generated files, so a prettier config is needed and prettier need to be installed. 50 | 51 | ## lockfile-lint 52 | 53 | ```sh 54 | node node_modules/tooling/lockfile-lint 55 | ``` 56 | 57 | Verifies the correctness of a yarn lockfile. 58 | Makes sure that all dependencies are provided from npm (e. g. not github dependencies). 59 | This is important as some users might not be able to access github. 60 | 61 | `yarn.lock` must be present in root directory. 62 | 63 | ## schemas-lint 64 | 65 | ```sh 66 | node node_modules/tooling/schemas-lint 67 | ``` 68 | 69 | Verifies the correctness of all JSON schemas. 70 | 71 | - Definitions and properties need to a `description` in the correct format. 72 | - Only allowed properties are used. 73 | - `$ref` must be the only property when provided. 74 | - `tsType` is needed when `instanceof` is used. 75 | - `type: "string"` is needed when `absolutePath` is used. 76 | - `additionalProperties` is needed when `properties` are used. 77 | 78 | ```text 79 | --schemas ./schemas/**/*.json 80 | ``` 81 | 82 | Glob to the schemas that should be processed. 83 | 84 | ## compile-to-definitions 85 | 86 | ```sh 87 | node node_modules/tooling/compile-to-definitions 88 | ``` 89 | 90 | Generates typescript declaration files from JSON schemas. 91 | 92 | ```text 93 | --schemas ./schemas/**/*.json 94 | ``` 95 | 96 | Glob to the schemas that should be processed. 97 | 98 | ```text 99 | ---declarations declarations 100 | ``` 101 | 102 | Output folder of the generated declaration files. 103 | 104 | ## precompile-schemas 105 | 106 | ```sh 107 | node node_modules/tooling/precompile-schemas 108 | ``` 109 | 110 | Generate `.check.js` files next to the JSON schemas. 111 | They export a single function which returned a boolean whether the object is validate according to the schema. 112 | This function doesn't do error handling, so run a real schema validation when it returns false. 113 | 114 | It will also generate a `.check.d.ts` file with type declarations. 115 | 116 | ```text 117 | --schemas ./schemas/**/*.json 118 | ``` 119 | 120 | Glob to the schemas that should be processed. 121 | 122 | ```text 123 | ---declarations declarations 124 | ``` 125 | 126 | Folder of the generated declaration files. 127 | 128 | ## inherit-types 129 | 130 | ```sh 131 | node node_modules/tooling/inherit-types 132 | ``` 133 | 134 | Synchronize jsdoc method annotations in classes with jsdoc of the same method in base class. 135 | This copies jsdoc one to one from base class, but omits `@abstract`. 136 | 137 | `tsconfig.json` must be present in root directory and typescript must be installed. 138 | 139 | ## format-file-header 140 | 141 | ```sh 142 | node node_modules/tooling/format-file-header 143 | ``` 144 | 145 | Ensures that the starting of all source files follows the following convention: 146 | 147 | ```js 148 | /* 149 | MIT License http://www.opensource.org/licenses/mit-license.php 150 | Author ... 151 | */ 152 | 153 | "use strict"; 154 | 155 | const Import = require("./Import"); 156 | const SortedAlphabetically = require("./SortedAlphabetically"); 157 | 158 | /** @typedef {import("../TypeImport")} TypeImport */ 159 | /** @typedef {import("../SortedAlphabetically")} SortedAlphabetically */ 160 | ``` 161 | 162 | ```text 163 | --source ./lib/**/*.js 164 | ``` 165 | 166 | Glob to the source that should be processed. 167 | 168 | ## format-schemas 169 | 170 | ```sh 171 | node node_modules/tooling/format-schemas 172 | ``` 173 | 174 | Sort JSON schema according to convention. 175 | 176 | ```text 177 | --schemas ./schemas/**/*.json 178 | ``` 179 | 180 | Glob to the schemas that should be processed. 181 | 182 | ## generate-types 183 | 184 | ```sh 185 | node node_modules/tooling/generate-types 186 | ``` 187 | 188 | Generate typescript types declarations file (`types.d.ts`) from the visible types in the exposed `main` (package.json) entry file. 189 | This declaration file should be used as `types` in `package.json`. 190 | 191 | When a `declarations/index.d.ts` file exists, types from this are also exposed. 192 | 193 | `tsconfig.types.json` must be present in root directory and typescript must be installed. 194 | 195 | ```text 196 | --types types.d.ts 197 | ``` 198 | 199 | Path of the generated declarations file. 200 | 201 | ## type-coverage 202 | 203 | ```sh 204 | node node_modules/tooling/generate-types 205 | ``` 206 | 207 | Generates type coverage raw coverage data in the `coverage` directory. 208 | `instanbul report` can be used to generate a readable report. 209 | 210 | `tsconfig.json` must be present in root directory and typescript must be installed. 211 | -------------------------------------------------------------------------------- /schemas-lint/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { root, schemas: schemasGlob } = require("../lib/argv"); 4 | 5 | const fs = require("fs"); 6 | const path = require("path"); 7 | const glob = require("glob"); 8 | 9 | const schemas = glob.sync(schemasGlob, { cwd: root }); 10 | 11 | for (const filename of schemas) { 12 | let content; 13 | 14 | try { 15 | const fileContent = fs.readFileSync(path.resolve(root, filename), "utf-8"); 16 | content = JSON.parse(fileContent); 17 | } catch (e) { 18 | console.log(`${filename} can't be parsed: ${e}`); 19 | process.exitCode = 1; 20 | } 21 | 22 | if (content) { 23 | const arrayProperties = ["oneOf", "anyOf", "allOf"]; 24 | const allowedProperties = [ 25 | "definitions", 26 | "$ref", 27 | "$id", 28 | "title", 29 | "cli", 30 | "items", 31 | "implements", 32 | "properties", 33 | "additionalProperties", 34 | "type", 35 | "oneOf", 36 | "anyOf", 37 | "absolutePath", 38 | "undefinedAsNull", 39 | "description", 40 | "enum", 41 | "minLength", 42 | "pattern", 43 | "minimum", 44 | "maximum", 45 | "required", 46 | "uniqueItems", 47 | "minItems", 48 | "minProperties", 49 | "instanceof", 50 | "tsType", 51 | "not", 52 | "link", 53 | ]; 54 | 55 | const isReference = (schema) => { 56 | return ( 57 | "$ref" in schema || 58 | ("oneOf" in schema && 59 | schema.oneOf.length === 1 && 60 | "$ref" in schema.oneOf[0]) 61 | ); 62 | }; 63 | 64 | const validateProperty = (path, property) => { 65 | if (isReference(property)) return; 66 | if ( 67 | typeof property.description !== "string" || 68 | property.description.length < 1 69 | ) { 70 | console.log(`${path} should have a description set.`); 71 | process.exitCode = 1; 72 | } else if (!/^[A-Z`].*[^\.]\.$/.test(property.description)) { 73 | console.log( 74 | `${path}.description should start with an uppercase letter and end with a single dot.`, 75 | ); 76 | process.exitCode = 1; 77 | } 78 | }; 79 | 80 | const walker = (path, item) => { 81 | const otherProperties = Object.keys(item).filter( 82 | (p) => allowedProperties.indexOf(p) < 0, 83 | ); 84 | if (otherProperties.length > 0) { 85 | console.log( 86 | `${path} should not have the ${ 87 | otherProperties.length > 1 ? "properties" : "property" 88 | } ${otherProperties.join(", ")}`, 89 | ); 90 | process.exitCode = 1; 91 | // When allowing more properties make sure to add nice error messages for them in WebpackOptionsValidationError 92 | } 93 | 94 | if ("$ref" in item) { 95 | const otherProperties = Object.keys(item).filter((p) => p !== "$ref"); 96 | if (otherProperties.length > 0) { 97 | console.log( 98 | `When using $ref not other properties are possible (${otherProperties.join( 99 | ", ", 100 | )})`, 101 | ); 102 | process.exitCode = 1; 103 | } 104 | } 105 | 106 | if ("type" in item) { 107 | if (typeof item.type !== "string") { 108 | console.log(`${path}: should have a single type`); 109 | process.exitCode = 1; 110 | } 111 | } 112 | 113 | if ("instanceof" in item) { 114 | if (!("tsType" in item)) { 115 | console.log(`${path}: When using instanceof, tsType is required`); 116 | process.exitCode = 1; 117 | } 118 | } 119 | 120 | if ("absolutePath" in item) { 121 | if (item.type !== "string") { 122 | console.log( 123 | `${path}: When using absolutePath, type must be 'string'`, 124 | ); 125 | process.exitCode = 1; 126 | } 127 | } 128 | 129 | if ("properties" in item || "additionalProperties" in item) { 130 | if (item.type !== "object") { 131 | console.log( 132 | `${path}: When using properties or additionalProperties, type must be 'object'`, 133 | ); 134 | process.exitCode = 1; 135 | } 136 | } 137 | 138 | arrayProperties.forEach((prop) => { 139 | if (prop in item) { 140 | if (item[prop].length === 0) { 141 | console.log(`${path} should not have empty one/any/allOf`); 142 | process.exitCode = 1; 143 | } else if ((item[prop].length === 1) !== (prop === "oneOf")) { 144 | if (prop === "oneOf") { 145 | console.log(`${path}: oneOf must have exactly one item`); 146 | } else { 147 | console.log(`${path}: ${prop} must have more than one item`); 148 | } 149 | process.exitCode = 1; 150 | } 151 | let i = 0; 152 | for (const subitem of item[prop]) { 153 | if (arrayProperties.some((prop) => prop in subitem)) { 154 | console.log(`${path} should not double nest one/any/allOf`); 155 | process.exitCode = 1; 156 | } 157 | walker(`${path}.${prop}[${i++}]`, subitem); 158 | } 159 | } 160 | }); 161 | if ("items" in item) { 162 | validateProperty(`${path}.items`, item.items); 163 | walker(`${path}.items`, item.items); 164 | } 165 | if ("definitions" in item) { 166 | Object.keys(item.definitions).forEach((name) => { 167 | const def = item.definitions[name]; 168 | validateProperty(`#${name}`, def); 169 | walker(`#${name}`, def); 170 | }); 171 | } 172 | if ("properties" in item) { 173 | if (item.additionalProperties === undefined) { 174 | console.log( 175 | `${path} should have additionalProperties set to some value when describing properties`, 176 | ); 177 | process.exitCode = 1; 178 | } 179 | Object.keys(item.properties).forEach((name) => { 180 | const property = item.properties[name]; 181 | validateProperty(`${path}.properties.${name}`, property); 182 | walker(`${path}.properties.${name}`, property); 183 | }); 184 | } 185 | if (typeof item.additionalProperties === "object") { 186 | validateProperty( 187 | `${path}.additionalProperties`, 188 | item.additionalProperties, 189 | ); 190 | walker(`${path}.additionalProperties`, item.additionalProperties); 191 | } 192 | }; 193 | 194 | walker(`[${filename}]`, content); 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /precompile-schemas/index.js: -------------------------------------------------------------------------------- 1 | const argv = require("../lib/argv"); 2 | 3 | const { write: doWrite, root, declarations, schemas: schemasGlob } = argv; 4 | 5 | const fs = require("fs"); 6 | const path = require("path"); 7 | const glob = require("glob"); 8 | const findCommonDir = require("commondir"); 9 | const { pathToFileURL, fileURLToPath } = require("url"); 10 | const processSchema = require("../lib/process-schema"); 11 | const terser = require("terser"); 12 | const { default: Ajv, _, Name } = require("ajv"); 13 | const standaloneCode = require("ajv/dist/standalone").default; 14 | 15 | const ajv = new Ajv({ 16 | code: { source: true, optimize: true }, 17 | messages: false, 18 | strictNumbers: false, 19 | logger: false, 20 | loadSchema: (uri) => { 21 | schemaPath = fileURLToPath(uri); 22 | const schema = require(schemaPath); 23 | const processedSchema = processJson(schema); 24 | processedSchema.$id = uri; 25 | return processedSchema; 26 | }, 27 | }); 28 | 29 | ajv.addKeyword({ 30 | keyword: "instanceof", 31 | schemaType: "string", 32 | code(ctx) { 33 | const { data, schema } = ctx; 34 | ctx.fail(_`!(${data} instanceof ${new Name(schema)})`); 35 | }, 36 | }); 37 | 38 | ajv.addKeyword({ 39 | keyword: "absolutePath", 40 | type: "string", 41 | schemaType: "boolean", 42 | 43 | code(ctx) { 44 | const { data, schema } = ctx; 45 | ctx.fail( 46 | _`${data}.includes("!") || (absolutePathRegExp.test(${data}) !== ${schema})`, 47 | ); 48 | }, 49 | }); 50 | 51 | ajv.removeKeyword("minLength"); 52 | ajv.addKeyword({ 53 | keyword: "minLength", 54 | type: "string", 55 | schemaType: "number", 56 | 57 | code(ctx) { 58 | const { data, schema } = ctx; 59 | if (schema !== 1) 60 | throw new Error("Schema precompilation only supports minLength: 1"); 61 | ctx.fail(_`${data}.length < 1`); 62 | }, 63 | }); 64 | 65 | ajv.addKeyword({ 66 | keyword: "undefinedAsNull", 67 | schemaType: "boolean", 68 | 69 | code(ctx) { 70 | // Nothing, just to avoid failing 71 | }, 72 | }); 73 | ajv.removeKeyword("enum"); 74 | ajv.addKeyword({ 75 | keyword: "enum", 76 | schemaType: "array", 77 | $data: true, 78 | 79 | code(ctx) { 80 | const { data, schema, parentSchema } = ctx; 81 | for (const item of schema) { 82 | if (typeof item === "object" && item !== null) { 83 | throw new Error( 84 | `Schema precompilation only supports primitive values in enum: ${JSON.stringify( 85 | item, 86 | null, 87 | 2, 88 | )}`, 89 | ); 90 | } 91 | } 92 | 93 | ctx.fail( 94 | schema 95 | .map((x) => { 96 | if (x === null && parentSchema.undefinedAsNull) { 97 | return _`${data} !== null && ${data} !== undefined`; 98 | } 99 | 100 | return _`${data} !== ${x}`; 101 | }) 102 | .reduce((a, b) => _`${a} && ${b}`), 103 | ); 104 | }, 105 | }); 106 | 107 | const schemas = glob.sync(schemasGlob, { cwd: root, absolute: true }); 108 | 109 | const EXCLUDED_PROPERTIES = [ 110 | "title", 111 | "description", 112 | "cli", 113 | "implements", 114 | "tsType", 115 | ]; 116 | 117 | const processJson = processSchema.bind(null, { 118 | schema: (json) => { 119 | for (const p of EXCLUDED_PROPERTIES) { 120 | delete json[p]; 121 | } 122 | return json; 123 | }, 124 | }); 125 | 126 | const postprocess = async (code) => { 127 | // add hoisted values 128 | if (/absolutePathRegExp/.test(code)) 129 | code = `const absolutePathRegExp = /^(?:[A-Za-z]:[\\\\/]|\\\\\\\\|\\/)/;${code}`; 130 | 131 | // remove unnecessary error code: 132 | code = code 133 | .replace(/\{instancePath[^{}]+,keyword:[^{}]+,/g, "{") 134 | // remove extra "$id" property 135 | .replace(/"\$id":".+?"/, ""); 136 | 137 | // minimize 138 | code = ( 139 | await terser.minify(code, { 140 | compress: { 141 | passes: 3, 142 | }, 143 | mangle: true, 144 | ecma: 2015, 145 | toplevel: true, 146 | }) 147 | ).code; 148 | 149 | // banner 150 | code = `/* 151 | * This file was automatically generated. 152 | * DO NOT MODIFY BY HAND. 153 | * Run \`yarn fix:special\` to update 154 | */ 155 | ${code}`; 156 | return code; 157 | }; 158 | 159 | const createDeclaration = (schemaPath, title, schemasDir) => { 160 | const relPath = path.relative(schemasDir, schemaPath); 161 | const directory = path.dirname(relPath); 162 | const basename = path.basename(relPath, path.extname(relPath)); 163 | const filename = path.resolve( 164 | root, 165 | declarations, 166 | `${path.join(directory, basename)}`, 167 | ); 168 | const fromSchemaToDeclaration = path 169 | .relative(path.dirname(schemaPath), filename) 170 | .replace(/\\/g, "/"); 171 | return `/* 172 | * This file was automatically generated. 173 | * DO NOT MODIFY BY HAND. 174 | * Run \`yarn fix:special\` to update 175 | */ 176 | declare const check: (options: ${ 177 | title 178 | ? `import(${JSON.stringify(fromSchemaToDeclaration)}).${title}` 179 | : "any" 180 | }) => boolean; 181 | export = check; 182 | `; 183 | }; 184 | 185 | const updateFile = (path, expected) => { 186 | let normalizedContent = ""; 187 | try { 188 | const content = fs.readFileSync(path, "utf-8"); 189 | normalizedContent = content.replace(/\r\n?/g, "\n"); 190 | } catch (e) { 191 | // ignore 192 | } 193 | if (normalizedContent.trim() !== expected.trim()) { 194 | if (doWrite) { 195 | fs.writeFileSync(path, expected, "utf-8"); 196 | console.error(`${path} updated`); 197 | } else { 198 | console.error(`${path} need to be updated\nExpected:\n${expected}`); 199 | process.exitCode = 1; 200 | } 201 | } 202 | }; 203 | 204 | const precompileSchema = async (schemaPath, schemasDir) => { 205 | if (path.basename(schemaPath).startsWith("_")) return; 206 | try { 207 | const schema = require(schemaPath); 208 | const title = schema.title; 209 | const processedSchema = processJson(schema); 210 | processedSchema.$id = pathToFileURL(schemaPath).href; 211 | const validate = await ajv.compileAsync(processedSchema); 212 | const code = await postprocess(standaloneCode(ajv, validate)); 213 | const precompiledSchemaPath = schemaPath.replace(/\.json$/, ".check.js"); 214 | const precompiledSchemaDeclarationPath = schemaPath.replace( 215 | /\.json$/, 216 | ".check.d.ts", 217 | ); 218 | updateFile(precompiledSchemaPath, code); 219 | updateFile( 220 | precompiledSchemaDeclarationPath, 221 | createDeclaration(schemaPath, title, schemasDir), 222 | ); 223 | } catch (e) { 224 | e.message += "\nduring precompilation of " + schemaPath; 225 | throw e; 226 | } 227 | }; 228 | 229 | (async () => { 230 | const commonDir = path.resolve(findCommonDir(schemas)); 231 | for (let absPath of schemas) { 232 | precompileSchema(absPath, commonDir); 233 | } 234 | })().catch((e) => { 235 | console.error(e.stack); 236 | process.exitCode = 1; 237 | }); 238 | -------------------------------------------------------------------------------- /format-file-header/index.js: -------------------------------------------------------------------------------- 1 | const argv = require("../lib/argv"); 2 | 3 | const { write: doWrite, root, source: sourceGlob } = argv; 4 | 5 | const path = require("path"); 6 | const fs = require("fs"); 7 | const glob = require("glob"); 8 | 9 | const allFiles = glob.sync(sourceGlob, { cwd: root, absolute: true }); 10 | 11 | let canUpdateWithWrite = false; 12 | 13 | const sortImport = (a, b) => { 14 | if (!a.key.startsWith(".") && b.key.startsWith(".")) return -1; 15 | if (a.key.startsWith(".") && !b.key.startsWith(".")) return 1; 16 | if (a.key < b.key) return -1; 17 | if (a.key > b.key) return 1; 18 | return 0; 19 | }; 20 | 21 | const execToArray = (content, regexp) => { 22 | const items = []; 23 | let match = regexp.exec(content); 24 | while (match) { 25 | items.push({ 26 | content: match[0], 27 | key: match[1] + match[2], 28 | }); 29 | match = regexp.exec(content); 30 | } 31 | return items; 32 | }; 33 | 34 | /** 35 | * @typedef {Object} Schema 36 | * @property {string} title 37 | * @property {RegExp} regexp 38 | * @property {string=} updateMessage 39 | * @property {boolean=} optional 40 | * @property {boolean=} repeat 41 | * @property {(...match: string[]) => string=} update 42 | */ 43 | 44 | /** @type {Schema[]} */ 45 | const schema = [ 46 | { 47 | title: "ts-ignore", 48 | regexp: /(\/\/ @ts-nocheck\n)?/g, 49 | }, 50 | { 51 | title: "license comment", 52 | regexp: 53 | /\/\*\n\s*MIT License http:\/\/www\.opensource\.org\/licenses\/mit-license\.php\n\s*(?:(Authors? .+)\n)?\s*\*\/\n/g, 54 | updateMessage: "update the license comment", 55 | update(content, author) { 56 | return ( 57 | [ 58 | "/*", 59 | "\tMIT License http://www.opensource.org/licenses/mit-license.php", 60 | author && `\t${author}`, 61 | "*/", 62 | ] 63 | .filter(Boolean) 64 | .join("\n") + "\n" 65 | ); 66 | }, 67 | }, 68 | { 69 | title: "new line after license comment", 70 | regexp: /\n?/g, 71 | updateMessage: "insert a new line after the license comment", 72 | update() { 73 | return "\n"; 74 | }, 75 | }, 76 | { 77 | title: "strict mode", 78 | regexp: /"use strict";\n/g, 79 | }, 80 | { 81 | title: "new line after strict mode", 82 | regexp: /\n?/g, 83 | updateMessage: 'insert a new line after "use strict"', 84 | update() { 85 | return "\n"; 86 | }, 87 | }, 88 | { 89 | title: "imports", 90 | regexp: 91 | /(const (\{\s+\w+(?::\s+\w+)?(?:,)?(,\s+\w+(?::\s+\w+)?(?:,)?)*\s+\}|\w+) = (\/\*\* @type \{TODO\} \*\/\s\()?require\("[^"]+"\)\)?(\.\w+)*;\n)+\n/g, 92 | updateMessage: "sort imports alphabetically", 93 | update(content) { 94 | const items = execToArray( 95 | content, 96 | /const (?:\{\s+\w+(?::\s+\w+)?(?:,)?(?:,\s+\w+(?::\s+\w+)?(?:,)?)*\s+\}|\w+) = (?:\/\*\* @type \{TODO\} \*\/\s\()?require\("([^"]+)"\)\)?((?:\.\w+)*);\n/g, 97 | ); 98 | items.sort(sortImport); 99 | return items.map((item) => item.content).join("") + "\n"; 100 | }, 101 | optional: true, 102 | repeat: true, 103 | }, 104 | { 105 | title: "type imports", 106 | regexp: 107 | /(\/\*\* (?:@template \w+ )*@typedef \{(?:typeof )?import\("[^"]+"\)(\.\w+)*(?:<(?:(?:\w\.)*\w+, )*(?:\w\.)*\w+>)?\} \w+(?:<(?:(?:\w\.)*\w+, )*(?:\w\.)*\w+>)? \*\/\n)+\n/g, 108 | updateMessage: "sort type imports alphabetically", 109 | update(content) { 110 | const items = execToArray( 111 | content, 112 | /\/\*\* (?:@template \w+ )*@typedef \{(?:typeof )?import\("([^"]+)"\)((?:\.\w+)*(?:<(?:(?:\w\.)*\w+, )*(?:\w\.)*\w+>)?)\} \w+(?:<(?:(?:\w\.)*\w+, )*(?:\w\.)*\w+>)? \*\/\n/g, 113 | ); 114 | items.sort(sortImport); 115 | return items.map((item) => item.content).join("") + "\n"; 116 | }, 117 | optional: true, 118 | repeat: true, 119 | }, 120 | ]; 121 | 122 | const allSerializables = new Set(); 123 | 124 | for (const filePath of allFiles) { 125 | let content = fs.readFileSync(filePath, "utf-8"); 126 | const nl = /(\r?\n)/.exec(content); 127 | content = content.replace(/\r?\n/g, "\n"); 128 | let newContent = content; 129 | 130 | let state = 0; 131 | let pos = 0; 132 | // eslint-disable-next-line no-constant-condition 133 | while (true) { 134 | const current = schema[state]; 135 | if (!current) break; 136 | current.regexp.lastIndex = pos; 137 | const match = current.regexp.exec(newContent); 138 | if (!match) { 139 | if (current.optional) { 140 | state++; 141 | continue; 142 | } 143 | console.log(`${filePath}: Missing ${current.title} at ${pos}`); 144 | process.exitCode = 1; 145 | break; 146 | } 147 | if (match.index !== pos) { 148 | console.log( 149 | `${filePath}: Unexpected code at ${pos}-${match.index}, expected ${current.title}`, 150 | ); 151 | process.exitCode = 1; 152 | pos = match.index; 153 | } 154 | if (!current.repeat) { 155 | state++; 156 | } 157 | if (current.update) { 158 | const update = current.update(...match); 159 | if (update !== match[0]) { 160 | newContent = 161 | newContent.slice(0, pos) + 162 | update + 163 | newContent.slice(pos + match[0].length); 164 | pos += update.length; 165 | if (!doWrite) { 166 | const updateMessage = 167 | current.updateMessage || `${current.title} need to be updated`; 168 | console.log(`${filePath}: ${updateMessage}`); 169 | } 170 | continue; 171 | } 172 | } 173 | pos += match[0].length; 174 | } 175 | 176 | if (newContent !== content) { 177 | if (doWrite) { 178 | if (nl) { 179 | newContent = newContent.replace(/\n/g, nl[0]); 180 | } 181 | fs.writeFileSync(filePath, newContent, "utf-8"); 182 | console.log(filePath); 183 | } else { 184 | console.log(filePath + " need to be updated."); 185 | canUpdateWithWrite = true; 186 | process.exitCode = 1; 187 | } 188 | } 189 | 190 | const matches = content.match( 191 | /makeSerializable\(\s*[^,]+,\s*"webpack\/lib\/[^"]+"\s*(?:,[^)]+)?\)/g, 192 | ); 193 | if (matches) { 194 | for (const match of matches) { 195 | const str = /makeSerializable\(\s*[^,]+,\s*"webpack\/lib\/([^"]+)"/.exec( 196 | match, 197 | )[1]; 198 | allSerializables.add(str); 199 | } 200 | } 201 | } 202 | 203 | // Check if internalSerializables.js includes all serializables in webpack 204 | for (const internalSerializables of allFiles.filter((file) => 205 | file.includes("internalSerializables"), 206 | )) { 207 | const content = fs.readFileSync(internalSerializables); 208 | for (const serializable of allSerializables) { 209 | if (!content.includes(`"../${serializable}"`)) { 210 | console.log( 211 | `${internalSerializables}: must include static require to ../${serializable}`, 212 | ); 213 | process.exitCode = 1; 214 | } 215 | } 216 | } 217 | 218 | if (canUpdateWithWrite) { 219 | console.log("Run 'yarn fix' to try to fix the problem automatically"); 220 | } 221 | -------------------------------------------------------------------------------- /compile-to-definitions/index.js: -------------------------------------------------------------------------------- 1 | const argv = require("../lib/argv"); 2 | 3 | const { 4 | write: doWrite, 5 | root, 6 | declarations: outputFolder, 7 | schemas: schemasGlob, 8 | } = argv; 9 | 10 | const fs = require("fs"); 11 | const path = require("path"); 12 | const prettier = require("prettier"); 13 | const glob = require("glob"); 14 | const findCommonDir = require("commondir"); 15 | const { compile } = require("json-schema-to-typescript"); 16 | 17 | const makeSchemas = () => { 18 | const schemas = glob.sync(schemasGlob, { cwd: root, absolute: true }); 19 | const commonDir = path.resolve(findCommonDir(schemas)); 20 | for (const absPath of schemas) { 21 | makeDefinitionsForSchema(absPath, commonDir); 22 | } 23 | }; 24 | 25 | const makeDefinitionsForSchema = async (absSchemaPath, schemasDir) => { 26 | if (path.basename(absSchemaPath).startsWith("_")) return; 27 | const relPath = path.relative(schemasDir, absSchemaPath); 28 | const directory = path.dirname(relPath); 29 | const basename = path.basename(relPath, path.extname(relPath)); 30 | const filename = path.resolve( 31 | root, 32 | outputFolder, 33 | `${path.join(directory, basename)}.d.ts`, 34 | ); 35 | const schema = JSON.parse(fs.readFileSync(absSchemaPath, "utf-8")); 36 | const keys = Object.keys(schema); 37 | if (keys.length === 1 && keys[0] === "$ref") return; 38 | 39 | const prettierConfig = await prettier.resolveConfig( 40 | path.resolve(root, outputFolder, "result.d.ts"), 41 | ); 42 | const style = { 43 | printWidth: prettierConfig.printWidth, 44 | useTabs: prettierConfig.useTabs, 45 | tabWidth: prettierConfig.tabWidth, 46 | }; 47 | 48 | preprocessSchema(schema); 49 | compile(schema, basename, { 50 | bannerComment: 51 | "/*\n * This file was automatically generated.\n * DO NOT MODIFY BY HAND.\n * Run `yarn fix:special` to update\n */", 52 | unreachableDefinitions: true, 53 | unknownAny: false, 54 | style, 55 | }).then( 56 | (ts) => { 57 | ts = ts.replace( 58 | /\s+\*\s+\* This interface was referenced by `.+`'s JSON-Schema\s+\* via the `definition` ".+"\./g, 59 | "", 60 | ); 61 | let normalizedContent = ""; 62 | try { 63 | const content = fs.readFileSync(filename, "utf-8"); 64 | normalizedContent = content.replace(/\r\n?/g, "\n"); 65 | } catch (e) { 66 | // ignore 67 | } 68 | if (normalizedContent.trim() !== ts.trim()) { 69 | if (doWrite) { 70 | fs.mkdirSync(path.dirname(filename), { recursive: true }); 71 | fs.writeFileSync(filename, ts, "utf-8"); 72 | console.error( 73 | `declarations/${relPath.replace(/\\/g, "/")}.d.ts updated`, 74 | ); 75 | } else { 76 | console.error( 77 | `declarations/${relPath.replace( 78 | /\\/g, 79 | "/", 80 | )}.d.ts need to be updated`, 81 | ); 82 | process.exitCode = 1; 83 | } 84 | } 85 | }, 86 | (err) => { 87 | console.error(err); 88 | process.exitCode = 1; 89 | }, 90 | ); 91 | }; 92 | 93 | const resolvePath = (root, ref) => { 94 | const parts = ref.split("/"); 95 | if (parts[0] !== "#") throw new Error("Unexpected ref"); 96 | let current = root; 97 | for (const p of parts.slice(1)) { 98 | current = current[p]; 99 | } 100 | return current; 101 | }; 102 | 103 | const preprocessSchema = (schema, root = schema, path = []) => { 104 | if ("definitions" in schema) { 105 | for (const key of Object.keys(schema.definitions)) { 106 | preprocessSchema(schema.definitions[key], root, [key]); 107 | } 108 | } 109 | if ("properties" in schema) { 110 | for (const key of Object.keys(schema.properties)) { 111 | const property = schema.properties[key]; 112 | if ("$ref" in property) { 113 | const result = resolvePath(root, property.$ref); 114 | if (!result) { 115 | throw new Error( 116 | `Unable to resolve "$ref": "${property.$ref}" in ${path.join("/")}`, 117 | ); 118 | } 119 | schema.properties[key] = { 120 | description: result.description, 121 | anyOf: [property], 122 | }; 123 | } else if ( 124 | "oneOf" in property && 125 | property.oneOf.length === 1 && 126 | "$ref" in property.oneOf[0] 127 | ) { 128 | const result = resolvePath(root, property.oneOf[0].$ref); 129 | schema.properties[key] = { 130 | description: property.description || result.description, 131 | anyOf: property.oneOf, 132 | }; 133 | preprocessSchema(schema.properties[key], root, [...path, key]); 134 | } else { 135 | preprocessSchema(property, root, [...path, key]); 136 | } 137 | } 138 | } 139 | if ("items" in schema) { 140 | preprocessSchema(schema.items, root, [...path, "item"]); 141 | } 142 | if (typeof schema.additionalProperties === "object") { 143 | preprocessSchema(schema.additionalProperties, root, [...path, "property"]); 144 | } 145 | const arrayProperties = ["oneOf", "anyOf", "allOf"]; 146 | for (const prop of arrayProperties) { 147 | if (Array.isArray(schema[prop])) { 148 | let i = 0; 149 | for (const item of schema[prop]) { 150 | preprocessSchema(item, root, [...path, item.type || i++]); 151 | } 152 | } 153 | } 154 | if ("type" in schema && schema.type === "array") { 155 | // Workaround for a typescript bug that 156 | // string[] is not assignable to [string, ...string] 157 | delete schema.minItems; 158 | } 159 | if ("implements" in schema) { 160 | const implementedProps = new Set(); 161 | const implementedNames = []; 162 | for (const impl of [].concat(schema.implements)) { 163 | const referencedSchema = resolvePath(root, impl); 164 | for (const prop of Object.keys(referencedSchema.properties)) { 165 | implementedProps.add(prop); 166 | } 167 | implementedNames.push(/\/([^\/]+)$/.exec(impl)[1]); 168 | } 169 | const propEntries = Object.entries(schema.properties).filter( 170 | ([name]) => !implementedProps.has(name), 171 | ); 172 | if (propEntries.length > 0) { 173 | const key = 174 | path.map((x) => x[0].toUpperCase() + x.slice(1)).join("") + "Extra"; 175 | implementedNames.push(key); 176 | const { implements, ...remainingSchema } = schema; 177 | root.definitions[key] = { 178 | ...remainingSchema, 179 | properties: Object.fromEntries(propEntries), 180 | }; 181 | preprocessSchema(root.definitions[key], root, [key]); 182 | } 183 | schema.tsType = implementedNames.join(" & "); 184 | return; 185 | } 186 | if ( 187 | "properties" in schema && 188 | typeof schema.additionalProperties === "object" && 189 | !schema.tsType 190 | ) { 191 | const { properties, additionalProperties, ...remaining } = schema; 192 | const key1 = 193 | path.map((x) => x[0].toUpperCase() + x.slice(1)).join("") + "Known"; 194 | const key2 = 195 | path.map((x) => x[0].toUpperCase() + x.slice(1)).join("") + "Unknown"; 196 | root.definitions[key1] = { 197 | ...remaining, 198 | properties, 199 | additionalProperties: false, 200 | }; 201 | preprocessSchema(root.definitions[key1], root, [key1]); 202 | root.definitions[key2] = { 203 | ...remaining, 204 | additionalProperties, 205 | }; 206 | preprocessSchema(root.definitions[key2], root, [key2]); 207 | schema.tsType = `${key1} & ${key2}`; 208 | return; 209 | } 210 | }; 211 | 212 | makeSchemas(); 213 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@apidevtools/json-schema-ref-parser@9.0.6": 6 | version "9.0.6" 7 | resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.6.tgz#5d9000a3ac1fd25404da886da6b266adcd99cf1c" 8 | integrity sha512-M3YgsLjI0lZxvrpeGVk9Ap032W6TPQkH6pRAZz81Ac3WUNF79VQooAFnp8umjvVzUmD93NkogxEwbSce7qMsUg== 9 | dependencies: 10 | "@jsdevtools/ono" "^7.1.3" 11 | call-me-maybe "^1.0.1" 12 | js-yaml "^3.13.1" 13 | 14 | "@jridgewell/gen-mapping@^0.3.5": 15 | version "0.3.5" 16 | resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" 17 | integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== 18 | dependencies: 19 | "@jridgewell/set-array" "^1.2.1" 20 | "@jridgewell/sourcemap-codec" "^1.4.10" 21 | "@jridgewell/trace-mapping" "^0.3.24" 22 | 23 | "@jridgewell/resolve-uri@^3.1.0": 24 | version "3.1.2" 25 | resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" 26 | integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== 27 | 28 | "@jridgewell/set-array@^1.2.1": 29 | version "1.2.1" 30 | resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" 31 | integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== 32 | 33 | "@jridgewell/source-map@^0.3.3": 34 | version "0.3.6" 35 | resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.6.tgz#9d71ca886e32502eb9362c9a74a46787c36df81a" 36 | integrity sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ== 37 | dependencies: 38 | "@jridgewell/gen-mapping" "^0.3.5" 39 | "@jridgewell/trace-mapping" "^0.3.25" 40 | 41 | "@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": 42 | version "1.5.0" 43 | resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" 44 | integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== 45 | 46 | "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": 47 | version "0.3.25" 48 | resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" 49 | integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== 50 | dependencies: 51 | "@jridgewell/resolve-uri" "^3.1.0" 52 | "@jridgewell/sourcemap-codec" "^1.4.14" 53 | 54 | "@jsdevtools/ono@^7.1.3": 55 | version "7.1.3" 56 | resolved "https://registry.yarnpkg.com/@jsdevtools/ono/-/ono-7.1.3.tgz#9df03bbd7c696a5c58885c34aa06da41c8543796" 57 | integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg== 58 | 59 | "@types/json-schema@^7.0.4": 60 | version "7.0.6" 61 | resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0" 62 | integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw== 63 | 64 | "@types/node@^22.5.4": 65 | version "22.5.4" 66 | resolved "https://registry.yarnpkg.com/@types/node/-/node-22.5.4.tgz#83f7d1f65bc2ed223bdbf57c7884f1d5a4fa84e8" 67 | integrity sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg== 68 | dependencies: 69 | undici-types "~6.19.2" 70 | 71 | "@types/yargs-parser@*": 72 | version "15.0.0" 73 | resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d" 74 | integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw== 75 | 76 | "@types/yargs@^15.0.12": 77 | version "15.0.12" 78 | resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.12.tgz#6234ce3e3e3fa32c5db301a170f96a599c960d74" 79 | integrity sha512-f+fD/fQAo3BCbCDlrUpznF1A5Zp9rB0noS5vnoormHSIPFKL0Z2DcUJ3Gxp5ytH4uLRNxy7AwYUC9exZzqGMAw== 80 | dependencies: 81 | "@types/yargs-parser" "*" 82 | 83 | "@yarnpkg/lockfile@^1.1.0": 84 | version "1.1.0" 85 | resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" 86 | integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== 87 | 88 | acorn@^8.8.2: 89 | version "8.12.1" 90 | resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" 91 | integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== 92 | 93 | ajv@^8.1.0: 94 | version "8.1.0" 95 | resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.1.0.tgz#45d5d3d36c7cdd808930cc3e603cf6200dbeb736" 96 | integrity sha512-B/Sk2Ix7A36fs/ZkuGLIR86EdjbgR6fsAcbx9lOP/QBSXujDNbVmIS/U4Itz5k8fPFDeVZl/zQ/gJW4Jrq6XjQ== 97 | dependencies: 98 | fast-deep-equal "^3.1.1" 99 | json-schema-traverse "^1.0.0" 100 | require-from-string "^2.0.2" 101 | uri-js "^4.2.2" 102 | 103 | ansi-regex@^2.1.1: 104 | version "2.1.1" 105 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" 106 | integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= 107 | 108 | ansi-regex@^5.0.0: 109 | version "5.0.0" 110 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" 111 | integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== 112 | 113 | ansi-styles@^4.0.0: 114 | version "4.3.0" 115 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" 116 | integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== 117 | dependencies: 118 | color-convert "^2.0.1" 119 | 120 | any-promise@^1.0.0: 121 | version "1.3.0" 122 | resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" 123 | integrity sha1-q8av7tzqUugJzcA3au0845Y10X8= 124 | 125 | argparse@^1.0.7: 126 | version "1.0.10" 127 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" 128 | integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== 129 | dependencies: 130 | sprintf-js "~1.0.2" 131 | 132 | balanced-match@^1.0.0: 133 | version "1.0.0" 134 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 135 | integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= 136 | 137 | brace-expansion@^1.1.7: 138 | version "1.1.11" 139 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 140 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 141 | dependencies: 142 | balanced-match "^1.0.0" 143 | concat-map "0.0.1" 144 | 145 | buffer-from@^1.0.0: 146 | version "1.1.1" 147 | resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" 148 | integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== 149 | 150 | call-me-maybe@^1.0.1: 151 | version "1.0.1" 152 | resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" 153 | integrity sha1-JtII6onje1y95gJQoV8DHBak1ms= 154 | 155 | cli-color@^2.0.0: 156 | version "2.0.0" 157 | resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-2.0.0.tgz#11ecfb58a79278cf6035a60c54e338f9d837897c" 158 | integrity sha512-a0VZ8LeraW0jTuCkuAGMNufareGHhyZU9z8OGsW0gXd1hZGi1SRuNRXdbGkraBBKnhyUhyebFWnRbp+dIn0f0A== 159 | dependencies: 160 | ansi-regex "^2.1.1" 161 | d "^1.0.1" 162 | es5-ext "^0.10.51" 163 | es6-iterator "^2.0.3" 164 | memoizee "^0.4.14" 165 | timers-ext "^0.1.7" 166 | 167 | cliui@^7.0.2: 168 | version "7.0.4" 169 | resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" 170 | integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== 171 | dependencies: 172 | string-width "^4.2.0" 173 | strip-ansi "^6.0.0" 174 | wrap-ansi "^7.0.0" 175 | 176 | color-convert@^2.0.1: 177 | version "2.0.1" 178 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" 179 | integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== 180 | dependencies: 181 | color-name "~1.1.4" 182 | 183 | color-name@~1.1.4: 184 | version "1.1.4" 185 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" 186 | integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== 187 | 188 | commander@^2.20.0: 189 | version "2.20.3" 190 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" 191 | integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== 192 | 193 | commondir@^1.0.1: 194 | version "1.0.1" 195 | resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" 196 | integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= 197 | 198 | concat-map@0.0.1: 199 | version "0.0.1" 200 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 201 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 202 | 203 | d@1, d@^1.0.1: 204 | version "1.0.1" 205 | resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" 206 | integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== 207 | dependencies: 208 | es5-ext "^0.10.50" 209 | type "^1.0.1" 210 | 211 | emoji-regex@^8.0.0: 212 | version "8.0.0" 213 | resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" 214 | integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== 215 | 216 | es5-ext@^0.10.35, es5-ext@^0.10.45, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.51, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46: 217 | version "0.10.53" 218 | resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1" 219 | integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q== 220 | dependencies: 221 | es6-iterator "~2.0.3" 222 | es6-symbol "~3.1.3" 223 | next-tick "~1.0.0" 224 | 225 | es6-iterator@^2.0.3, es6-iterator@~2.0.3: 226 | version "2.0.3" 227 | resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" 228 | integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= 229 | dependencies: 230 | d "1" 231 | es5-ext "^0.10.35" 232 | es6-symbol "^3.1.1" 233 | 234 | es6-symbol@^3.1.1, es6-symbol@~3.1.3: 235 | version "3.1.3" 236 | resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" 237 | integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== 238 | dependencies: 239 | d "^1.0.1" 240 | ext "^1.1.2" 241 | 242 | es6-weak-map@^2.0.2: 243 | version "2.0.3" 244 | resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.3.tgz#b6da1f16cc2cc0d9be43e6bdbfc5e7dfcdf31d53" 245 | integrity sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA== 246 | dependencies: 247 | d "1" 248 | es5-ext "^0.10.46" 249 | es6-iterator "^2.0.3" 250 | es6-symbol "^3.1.1" 251 | 252 | escalade@^3.1.1: 253 | version "3.1.1" 254 | resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" 255 | integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== 256 | 257 | esprima@^4.0.0: 258 | version "4.0.1" 259 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" 260 | integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== 261 | 262 | event-emitter@^0.3.5: 263 | version "0.3.5" 264 | resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" 265 | integrity sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk= 266 | dependencies: 267 | d "1" 268 | es5-ext "~0.10.14" 269 | 270 | ext@^1.1.2: 271 | version "1.4.0" 272 | resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" 273 | integrity sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A== 274 | dependencies: 275 | type "^2.0.0" 276 | 277 | fast-deep-equal@^3.1.1: 278 | version "3.1.3" 279 | resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" 280 | integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== 281 | 282 | fs.realpath@^1.0.0: 283 | version "1.0.0" 284 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 285 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= 286 | 287 | get-caller-file@^2.0.5: 288 | version "2.0.5" 289 | resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" 290 | integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== 291 | 292 | glob@^7.1.6: 293 | version "7.1.6" 294 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" 295 | integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== 296 | dependencies: 297 | fs.realpath "^1.0.0" 298 | inflight "^1.0.4" 299 | inherits "2" 300 | minimatch "^3.0.4" 301 | once "^1.3.0" 302 | path-is-absolute "^1.0.0" 303 | 304 | inflight@^1.0.4: 305 | version "1.0.6" 306 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 307 | integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= 308 | dependencies: 309 | once "^1.3.0" 310 | wrappy "1" 311 | 312 | inherits@2: 313 | version "2.0.4" 314 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 315 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 316 | 317 | is-extglob@^2.1.1: 318 | version "2.1.1" 319 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" 320 | integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= 321 | 322 | is-fullwidth-code-point@^3.0.0: 323 | version "3.0.0" 324 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" 325 | integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== 326 | 327 | is-glob@^4.0.1: 328 | version "4.0.1" 329 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" 330 | integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== 331 | dependencies: 332 | is-extglob "^2.1.1" 333 | 334 | is-promise@^2.1: 335 | version "2.2.2" 336 | resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" 337 | integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== 338 | 339 | js-yaml@^3.13.1: 340 | version "3.14.0" 341 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" 342 | integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== 343 | dependencies: 344 | argparse "^1.0.7" 345 | esprima "^4.0.0" 346 | 347 | json-schema-ref-parser@^9.0.1: 348 | version "9.0.6" 349 | resolved "https://registry.yarnpkg.com/json-schema-ref-parser/-/json-schema-ref-parser-9.0.6.tgz#fc89a5e6b853f2abe8c0af30d3874196526adb60" 350 | integrity sha512-z0JGv7rRD3CnJbZY/qCpscyArdtLJhr/wRBmFUdoZ8xMjsFyNdILSprG2degqRLjBjyhZHAEBpGOxniO9rKTxA== 351 | dependencies: 352 | "@apidevtools/json-schema-ref-parser" "9.0.6" 353 | 354 | json-schema-to-typescript@^9.1.1: 355 | version "9.1.1" 356 | resolved "https://registry.yarnpkg.com/json-schema-to-typescript/-/json-schema-to-typescript-9.1.1.tgz#572c1eb8b7ca82d6534c023c4651f3fe925171c0" 357 | integrity sha512-VrdxmwQROjPBRlHxXwGUa2xzhOMPiNZIVsxZrZjMYtbI7suRFMiEktqaD/gqhfSya7Djy+x8dnJT+H0/0sZO0Q== 358 | dependencies: 359 | "@types/json-schema" "^7.0.4" 360 | cli-color "^2.0.0" 361 | glob "^7.1.6" 362 | is-glob "^4.0.1" 363 | json-schema-ref-parser "^9.0.1" 364 | json-stringify-safe "^5.0.1" 365 | lodash "^4.17.15" 366 | minimist "^1.2.5" 367 | mkdirp "^1.0.4" 368 | mz "^2.7.0" 369 | prettier "^2.0.5" 370 | stdin "0.0.1" 371 | 372 | json-schema-traverse@^1.0.0: 373 | version "1.0.0" 374 | resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" 375 | integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== 376 | 377 | json-stringify-safe@^5.0.1: 378 | version "5.0.1" 379 | resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" 380 | integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= 381 | 382 | lodash@^4.17.15: 383 | version "4.17.20" 384 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" 385 | integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== 386 | 387 | lru-queue@0.1: 388 | version "0.1.0" 389 | resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3" 390 | integrity sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM= 391 | dependencies: 392 | es5-ext "~0.10.2" 393 | 394 | memoizee@^0.4.14: 395 | version "0.4.14" 396 | resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.14.tgz#07a00f204699f9a95c2d9e77218271c7cd610d57" 397 | integrity sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg== 398 | dependencies: 399 | d "1" 400 | es5-ext "^0.10.45" 401 | es6-weak-map "^2.0.2" 402 | event-emitter "^0.3.5" 403 | is-promise "^2.1" 404 | lru-queue "0.1" 405 | next-tick "1" 406 | timers-ext "^0.1.5" 407 | 408 | minimatch@^3.0.4: 409 | version "3.0.4" 410 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 411 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== 412 | dependencies: 413 | brace-expansion "^1.1.7" 414 | 415 | minimist@^1.2.5: 416 | version "1.2.5" 417 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" 418 | integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== 419 | 420 | mkdirp@^1.0.4: 421 | version "1.0.4" 422 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" 423 | integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== 424 | 425 | mz@^2.7.0: 426 | version "2.7.0" 427 | resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" 428 | integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== 429 | dependencies: 430 | any-promise "^1.0.0" 431 | object-assign "^4.0.1" 432 | thenify-all "^1.0.0" 433 | 434 | next-tick@1: 435 | version "1.1.0" 436 | resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" 437 | integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== 438 | 439 | next-tick@~1.0.0: 440 | version "1.0.0" 441 | resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" 442 | integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= 443 | 444 | object-assign@^4.0.1: 445 | version "4.1.1" 446 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 447 | integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= 448 | 449 | once@^1.3.0: 450 | version "1.4.0" 451 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 452 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 453 | dependencies: 454 | wrappy "1" 455 | 456 | path-is-absolute@^1.0.0: 457 | version "1.0.1" 458 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 459 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 460 | 461 | prettier@^2.0.5: 462 | version "2.2.0" 463 | resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.2.0.tgz#8a03c7777883b29b37fb2c4348c66a78e980418b" 464 | integrity sha512-yYerpkvseM4iKD/BXLYUkQV5aKt4tQPqaGW6EsZjzyu0r7sVZZNPJW4Y8MyKmicp6t42XUPcBVA+H6sB3gqndw== 465 | 466 | prettier@^3.0.1: 467 | version "3.0.1" 468 | resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.1.tgz#65271fc9320ce4913c57747a70ce635b30beaa40" 469 | integrity sha512-fcOWSnnpCrovBsmFZIGIy9UqK2FaI7Hqax+DIO0A9UxeVoY4iweyaFjS5TavZN97Hfehph0nhsZnjlVKzEQSrQ== 470 | 471 | punycode@^2.1.0: 472 | version "2.1.1" 473 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" 474 | integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== 475 | 476 | require-directory@^2.1.1: 477 | version "2.1.1" 478 | resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" 479 | integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= 480 | 481 | require-from-string@^2.0.2: 482 | version "2.0.2" 483 | resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" 484 | integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== 485 | 486 | source-map-support@~0.5.20: 487 | version "0.5.21" 488 | resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" 489 | integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== 490 | dependencies: 491 | buffer-from "^1.0.0" 492 | source-map "^0.6.0" 493 | 494 | source-map@^0.6.0: 495 | version "0.6.1" 496 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" 497 | integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== 498 | 499 | sprintf-js@~1.0.2: 500 | version "1.0.3" 501 | resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" 502 | integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= 503 | 504 | stdin@0.0.1: 505 | version "0.0.1" 506 | resolved "https://registry.yarnpkg.com/stdin/-/stdin-0.0.1.tgz#d3041981aaec3dfdbc77a1b38d6372e38f5fb71e" 507 | integrity sha1-0wQZgarsPf28d6GzjWNy449ftx4= 508 | 509 | string-width@^4.1.0, string-width@^4.2.0: 510 | version "4.2.0" 511 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" 512 | integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== 513 | dependencies: 514 | emoji-regex "^8.0.0" 515 | is-fullwidth-code-point "^3.0.0" 516 | strip-ansi "^6.0.0" 517 | 518 | strip-ansi@^6.0.0: 519 | version "6.0.0" 520 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" 521 | integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== 522 | dependencies: 523 | ansi-regex "^5.0.0" 524 | 525 | terser@^5.32.0: 526 | version "5.32.0" 527 | resolved "https://registry.yarnpkg.com/terser/-/terser-5.32.0.tgz#ee811c0d2d6b741c1cc34a2bc5bcbfc1b5b1f96c" 528 | integrity sha512-v3Gtw3IzpBJ0ugkxEX8U0W6+TnPKRRCWGh1jC/iM/e3Ki5+qvO1L1EAZ56bZasc64aXHwRHNIQEzm6//i5cemQ== 529 | dependencies: 530 | "@jridgewell/source-map" "^0.3.3" 531 | acorn "^8.8.2" 532 | commander "^2.20.0" 533 | source-map-support "~0.5.20" 534 | 535 | thenify-all@^1.0.0: 536 | version "1.6.0" 537 | resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" 538 | integrity sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY= 539 | dependencies: 540 | thenify ">= 3.1.0 < 4" 541 | 542 | "thenify@>= 3.1.0 < 4": 543 | version "3.3.1" 544 | resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" 545 | integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== 546 | dependencies: 547 | any-promise "^1.0.0" 548 | 549 | timers-ext@^0.1.5, timers-ext@^0.1.7: 550 | version "0.1.7" 551 | resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.7.tgz#6f57ad8578e07a3fb9f91d9387d65647555e25c6" 552 | integrity sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ== 553 | dependencies: 554 | es5-ext "~0.10.46" 555 | next-tick "1" 556 | 557 | type@^1.0.1: 558 | version "1.2.0" 559 | resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" 560 | integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== 561 | 562 | type@^2.0.0: 563 | version "2.1.0" 564 | resolved "https://registry.yarnpkg.com/type/-/type-2.1.0.tgz#9bdc22c648cf8cf86dd23d32336a41cfb6475e3f" 565 | integrity sha512-G9absDWvhAWCV2gmF1zKud3OyC61nZDwWvBL2DApaVFogI07CprggiQAOOjvp2NRjYWFzPyu7vwtDrQFq8jeSA== 566 | 567 | typescript@^5.6.2: 568 | version "5.6.2" 569 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.2.tgz#d1de67b6bef77c41823f822df8f0b3bcff60a5a0" 570 | integrity sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw== 571 | 572 | undici-types@~6.19.2: 573 | version "6.19.8" 574 | resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" 575 | integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== 576 | 577 | uri-js@^4.2.2: 578 | version "4.4.1" 579 | resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" 580 | integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== 581 | dependencies: 582 | punycode "^2.1.0" 583 | 584 | wrap-ansi@^7.0.0: 585 | version "7.0.0" 586 | resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" 587 | integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== 588 | dependencies: 589 | ansi-styles "^4.0.0" 590 | string-width "^4.1.0" 591 | strip-ansi "^6.0.0" 592 | 593 | wrappy@1: 594 | version "1.0.2" 595 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 596 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 597 | 598 | y18n@^5.0.5: 599 | version "5.0.5" 600 | resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.5.tgz#8769ec08d03b1ea2df2500acef561743bbb9ab18" 601 | integrity sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg== 602 | 603 | yargs-parser@^20.2.2: 604 | version "20.2.4" 605 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" 606 | integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== 607 | 608 | yargs@^16.1.1: 609 | version "16.1.1" 610 | resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.1.1.tgz#5a4a095bd1ca806b0a50d0c03611d38034d219a1" 611 | integrity sha512-hAD1RcFP/wfgfxgMVswPE+z3tlPFtxG8/yWUrG2i17sTWGCGqWnxKcLTF4cUKDUK8fzokwsmO9H0TDkRbMHy8w== 612 | dependencies: 613 | cliui "^7.0.2" 614 | escalade "^3.1.1" 615 | get-caller-file "^2.0.5" 616 | require-directory "^2.1.1" 617 | string-width "^4.2.0" 618 | y18n "^5.0.5" 619 | yargs-parser "^20.2.2" 620 | -------------------------------------------------------------------------------- /generate-types/index.js: -------------------------------------------------------------------------------- 1 | const argv = require("../lib/argv"); 2 | 3 | const { 4 | write: doWrite, 5 | verbose, 6 | root, 7 | types: outputFile, 8 | templateLiterals, 9 | } = argv; 10 | 11 | const path = require("path"); 12 | const fs = require("fs").promises; 13 | const ts = require("typescript"); 14 | const prettier = require("prettier"); 15 | 16 | process.exitCode = 1; 17 | let exitCode = 0; 18 | 19 | const AnonymousType = "__Type"; 20 | 21 | const IDENTIFIER_NAME_REPLACE_REGEX = /^([^a-zA-Z$_])/; 22 | const IDENTIFIER_ALPHA_NUMERIC_NAME_REPLACE_REGEX = /[^a-zA-Z0-9$]+/g; 23 | const toIdentifier = (str) => { 24 | return str 25 | .replace(IDENTIFIER_NAME_REPLACE_REGEX, "_$1") 26 | .replace(IDENTIFIER_ALPHA_NUMERIC_NAME_REPLACE_REGEX, "_"); 27 | }; 28 | 29 | const joinIdentifer = (list) => { 30 | const str = list.join("_"); 31 | return str.replace(/([^_])_+(.|$)/g, (m, a, b) => 32 | a !== a.toLowerCase() ? `${a}_${b}` : a + b.toUpperCase(), 33 | ); 34 | }; 35 | 36 | const flatten = (iterable) => { 37 | const array = []; 38 | for (const list of iterable) { 39 | array.push(...list); 40 | } 41 | return array; 42 | }; 43 | 44 | const quoteMeta = (str) => { 45 | return str.replace(/[-[\]\\/{}()*+?.^$|]/g, "\\$&"); 46 | }; 47 | 48 | class TupleMap { 49 | constructor() { 50 | /** @type {Map} */ 51 | this.map = new Map(); 52 | } 53 | 54 | /** 55 | * @param {any[]} tuple tuple 56 | * @returns {any} value or undefined 57 | */ 58 | get(tuple) { 59 | return this._get(tuple, 0); 60 | } 61 | 62 | _get(tuple, index) { 63 | const entry = this.map.get(tuple[index]); 64 | if (entry === undefined) return undefined; 65 | if (tuple.length === index + 1) return entry.value; 66 | if (entry.map === undefined) return undefined; 67 | return entry.map._get(tuple, index + 1); 68 | } 69 | 70 | /** 71 | * @param {any[]} tuple tuple 72 | * @returns {boolean} true, if it's in the map 73 | */ 74 | has(tuple) { 75 | return this._has(tuple, 0); 76 | } 77 | 78 | _has(tuple, index) { 79 | const entry = this.map.get(tuple[index]); 80 | if (entry === undefined) return false; 81 | if (tuple.length === index + 1) return entry.hasValue; 82 | if (entry.map === undefined) return false; 83 | return entry.map._has(tuple, index + 1); 84 | } 85 | 86 | /** 87 | * @param {any[]} tuple tuple 88 | * @param {any} value the new value 89 | * @returns {void} 90 | */ 91 | set(tuple, value) { 92 | return this._set(tuple, 0, value); 93 | } 94 | 95 | _set(tuple, index, value) { 96 | let entry = this.map.get(tuple[index]); 97 | if (entry === undefined) { 98 | entry = { map: undefined, hasValue: false, value: undefined }; 99 | this.map.set(tuple[index], entry); 100 | } 101 | if (tuple.length === index + 1) { 102 | entry.hasValue = true; 103 | entry.value = value; 104 | return; 105 | } 106 | if (entry.map === undefined) { 107 | entry.map = new TupleMap(); 108 | } 109 | entry.map._set(tuple, index + 1, value); 110 | } 111 | 112 | /** 113 | * @param {any[]} tuple tuple 114 | * @returns {void} 115 | */ 116 | add(tuple) { 117 | return this._add(tuple, 0); 118 | } 119 | 120 | _add(tuple, index) { 121 | let entry = this.map.get(tuple[index]); 122 | if (entry === undefined) { 123 | entry = { map: undefined, hasValue: false, value: undefined }; 124 | this.map.set(tuple[index], entry); 125 | } 126 | if (tuple.length === index + 1) { 127 | entry.hasValue = true; 128 | entry.value = undefined; 129 | return; 130 | } 131 | if (entry.map === undefined) { 132 | entry.map = new TupleMap(); 133 | } 134 | entry.map._add(tuple, index + 1); 135 | } 136 | 137 | /** 138 | * @param {any[]} tuple tuple 139 | * @returns {void} 140 | */ 141 | delete(tuple) { 142 | return this._delete(tuple, 0); 143 | } 144 | 145 | _delete(tuple, index) { 146 | const entry = this.map.get(tuple[index]); 147 | if (entry === undefined) { 148 | return; 149 | } 150 | if (tuple.length === index + 1) { 151 | entry.hasValue = false; 152 | entry.value = undefined; 153 | if (entry.map === undefined) { 154 | this.map.delete(tuple[index]); 155 | } 156 | return; 157 | } 158 | if (entry.map === undefined) { 159 | return; 160 | } 161 | entry.map._delete(tuple, index + 1); 162 | if (entry.map.map.size === 0) { 163 | entry.map = undefined; 164 | if (!entry.hasValue) { 165 | this.map.delete(tuple[index]); 166 | } 167 | } 168 | } 169 | 170 | values() { 171 | const values = []; 172 | for (const entry of this.map.values()) { 173 | if (entry.hasValue) values.push(entry.value); 174 | if (entry.map !== undefined) { 175 | values.push(...entry.map.values()); 176 | } 177 | } 178 | return values; 179 | } 180 | } 181 | 182 | /** 183 | * @param {ts.Diagnostic} diagnostic info 184 | * @returns {void} 185 | */ 186 | const printError = (diagnostic) => { 187 | if (diagnostic.file && typeof diagnostic.start === "number") { 188 | let { line, character } = diagnostic.file.getLineAndCharacterOfPosition( 189 | diagnostic.start, 190 | ); 191 | let message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"); 192 | console.error( 193 | `${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`, 194 | ); 195 | } else { 196 | console.error( 197 | ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"), 198 | ); 199 | } 200 | }; 201 | 202 | (async () => { 203 | const rootPath = path.resolve(root); 204 | 205 | const ownConfigPath = path.resolve(rootPath, "generate-types-config.js"); 206 | const options = { 207 | nameMapping: {}, 208 | typeMapping: [], 209 | exclude: [], 210 | include: [], 211 | }; 212 | try { 213 | Object.assign(options, require(ownConfigPath)); 214 | } catch (e) { 215 | if (verbose) { 216 | console.log(`Can't read config file: ${e}`); 217 | } 218 | } 219 | 220 | const configPath = path.resolve(rootPath, "tsconfig.types.json"); 221 | const configContent = ts.sys.readFile(configPath); 222 | if (!configContent) { 223 | console.error("Empty config file"); 224 | return; 225 | } 226 | const configJsonFile = ts.parseJsonText(configPath, configContent); 227 | const parsedConfig = ts.parseJsonSourceFileConfigFileContent( 228 | configJsonFile, 229 | ts.sys, 230 | rootPath, 231 | { 232 | noEmit: true, 233 | }, 234 | ); 235 | 236 | if (parsedConfig.errors && parsedConfig.errors.length > 0) { 237 | for (const error of parsedConfig.errors) { 238 | printError(error); 239 | } 240 | return; 241 | } 242 | 243 | const program = ts.createProgram( 244 | parsedConfig.fileNames, 245 | parsedConfig.options, 246 | ); 247 | 248 | const checker = program.getTypeChecker(); 249 | 250 | const exposedFiles = ["lib/index.js"]; 251 | 252 | try { 253 | if ( 254 | ( 255 | await fs.stat(path.resolve(rootPath, "declarations/index.d.ts")) 256 | ).isFile() 257 | ) { 258 | exposedFiles.push("declarations/index.d.ts"); 259 | } 260 | } catch {} 261 | 262 | /** @type {Set} */ 263 | const collectedTypes = new Set(); 264 | /** @type {Map} */ 265 | const typeNameHints = new Map(); 266 | /** @type {Map>} */ 267 | const typeReferencedBy = new Map(); 268 | 269 | /** 270 | * @param {ts.Type | undefined} from source type 271 | * @param {ts.Type} type type 272 | * @param {ts.SourceFile=} source source file 273 | * @param {ts.Symbol=} symbol declaration symbol 274 | * @param {string=} name name hint 275 | * @returns {void} 276 | */ 277 | const captureType = (from, type, source, symbol, name) => { 278 | if (from) { 279 | const set = typeReferencedBy.get(type); 280 | if (set === undefined) { 281 | typeReferencedBy.set(type, new Set([from])); 282 | } else { 283 | set.add(from); 284 | } 285 | } 286 | if (typeNameHints.has(type)) return; 287 | collectedTypes.add(type); 288 | const hint = typeNameHints.get(type); 289 | if (!hint) return; 290 | typeNameHints.set(type, { 291 | source: source || hint.source, 292 | symbol: symbol || hint.symbol, 293 | name: name || hint.name, 294 | }); 295 | }; 296 | 297 | /** 298 | * @param {ts.SourceFile} source source file 299 | * @returns {ts.Type} the type 300 | */ 301 | const getTypeOfSourceFile = (source) => { 302 | const sourceAsAny = /** @type {any} */ (source); 303 | if ( 304 | sourceAsAny.externalModuleIndicator || 305 | sourceAsAny.commonJsModuleIndicator 306 | ) { 307 | const moduleSymbol = /** @type {ts.Symbol} */ (sourceAsAny.symbol); 308 | return checker.getTypeOfSymbolAtLocation(moduleSymbol, source); 309 | } 310 | throw new Error("Not a module"); 311 | }; 312 | 313 | /** 314 | * @param {ts.SourceFile} source source file 315 | * @returns {Map} type exports 316 | */ 317 | const getTypeExportsOfSourceFile = (source) => { 318 | /** @type {Map} */ 319 | const map = new Map(); 320 | if (isSourceFileModule(source)) { 321 | const sourceAsAny = /** @type {any} */ (source); 322 | const moduleSymbol = /** @type {ts.Symbol} */ (sourceAsAny.symbol); 323 | if (!moduleSymbol.exports) throw new Error("Not a module namespace"); 324 | moduleSymbol.exports.forEach((symbol, name) => { 325 | if (name === ts.InternalSymbolName.ExportEquals) return; 326 | const exportName = ts.unescapeLeadingUnderscores(name); 327 | if (exportName.startsWith("_")) return; 328 | const type = checker.getDeclaredTypeOfSymbol(symbol); 329 | if (type.getFlags() & ts.TypeFlags.Any) return; 330 | type.aliasSymbol = symbol; 331 | map.set(exportName, type); 332 | }); 333 | return map; 334 | } 335 | throw new Error("Not a module"); 336 | }; 337 | 338 | /** 339 | * @param {ts.SourceFile} source source file 340 | * @returns {boolean} true when it's a module 341 | */ 342 | const isSourceFileModule = (source) => { 343 | const sourceAsAny = /** @type {any} */ (source); 344 | return ( 345 | sourceAsAny.externalModuleIndicator || sourceAsAny.commonJsModuleIndicator 346 | ); 347 | }; 348 | 349 | /** 350 | * @param {ts.Symbol} current current 351 | * @returns {string} full escaped name 352 | */ 353 | const getFullEscapedName = (current) => { 354 | let name = current.escapedName.toString(); 355 | while (current.parent) { 356 | current = current.parent; 357 | if ( 358 | current.escapedName === undefined || 359 | current.escapedName.toString() === "__global" 360 | ) 361 | break; 362 | name = `${current.escapedName.toString()}.${name}`; 363 | } 364 | return name; 365 | }; 366 | 367 | const getTypeOfSymbol = (symbol, isValue) => { 368 | let decl; 369 | const type = (() => { 370 | let type; 371 | if (!isValue) { 372 | type = checker.getDeclaredTypeOfSymbol(symbol); 373 | if (type && type.intrinsicName !== "error") { 374 | return type; 375 | } 376 | } 377 | if (symbol.type) return symbol.type; 378 | const decls = symbol.getDeclarations(); 379 | decl = decls && decls[0]; 380 | if (decl) { 381 | type = checker.getTypeOfSymbolAtLocation(symbol, decl); 382 | if (type && type.intrinsicName !== "error") { 383 | return type; 384 | } 385 | type = checker.getTypeAtLocation(decl); 386 | if (type && type.intrinsicName !== "error") { 387 | return type; 388 | } 389 | } 390 | })(); 391 | if (type && decl) { 392 | // Learn about type nodes 393 | if ( 394 | ((ts.isTypeAliasDeclaration(decl) && !decl.typeParameters) || 395 | (ts.isParameter(decl) && !decl.questionToken)) && 396 | decl.type 397 | ) { 398 | /** @type {any} */ (type)._typeNode = decl.type; 399 | } 400 | if (ts.isParameter(decl)) { 401 | for (const tag of ts.getJSDocTags(decl)) { 402 | if ( 403 | ts.isJSDocParameterTag(tag) && 404 | tag.typeExpression && 405 | ts.isJSDocTypeExpression(tag.typeExpression) 406 | ) { 407 | /** @type {any} */ (type)._typeNode = tag.typeExpression.type; 408 | } 409 | } 410 | } 411 | } 412 | return type; 413 | }; 414 | 415 | const getDeclaration = (symbol) => { 416 | if (!symbol) return undefined; 417 | /** @type {ts.Declaration | undefined} */ 418 | let decl = undefined; 419 | if (symbol.valueDeclaration) { 420 | decl = symbol.valueDeclaration; 421 | } else { 422 | const decls = symbol.getDeclarations(); 423 | if (decls) decl = decls[0]; 424 | } 425 | if (decl) { 426 | const symbol = /** @type {any} */ (decl).symbol; 427 | if (symbol && symbol.name === ts.InternalSymbolName.Type && decl.parent) { 428 | const parent = decl.parent; 429 | if (parent.kind === ts.SyntaxKind.TypeAliasDeclaration) { 430 | decl = /** @type {ts.Declaration} */ (parent); 431 | } 432 | if ( 433 | parent.kind === ts.SyntaxKind.JSDocTypeExpression && 434 | parent.parent && 435 | parent.parent.kind === ts.SyntaxKind.JSDocTypedefTag 436 | ) { 437 | decl = /** @type {ts.Declaration} */ (parent.parent); 438 | } 439 | } 440 | } 441 | 442 | return decl; 443 | }; 444 | 445 | /** 446 | * @param {ts.SourceFile} source source file 447 | * @returns {Map} exposed symbols with their names 448 | */ 449 | const getExportsOfSourceFile = (source) => { 450 | // TODO caching 451 | /** @type Map */ 452 | const map = new Map(); 453 | const sourceAsAny = /** @type {any} */ (source); 454 | if (isSourceFileModule(source)) { 455 | const moduleSymbol = /** @type {ts.Symbol} */ (sourceAsAny.symbol); 456 | if (moduleSymbol && moduleSymbol.exports) { 457 | moduleSymbol.exports.forEach((symbol, name) => { 458 | map.set(symbol, ts.unescapeLeadingUnderscores(name)); 459 | }); 460 | } 461 | } else { 462 | for (const [name, symbol] of sourceAsAny.locals) { 463 | map.set(symbol, name); 464 | } 465 | } 466 | // Expand namespaces 467 | for (const [symbol, namespaceName] of map) { 468 | const flags = symbol.getFlags(); 469 | if ( 470 | flags & ts.SymbolFlags.NamespaceModule || 471 | flags & ts.SymbolFlags.ValueModule 472 | ) { 473 | if (symbol.exports) { 474 | symbol.exports.forEach((symbol, name) => { 475 | map.set( 476 | symbol, 477 | `${namespaceName}.${ts.unescapeLeadingUnderscores(name)}`, 478 | ); 479 | }); 480 | } 481 | } 482 | } 483 | // Expand references 484 | for (const [symbol, name] of map) { 485 | const decl = getDeclaration(symbol); 486 | if (decl && decl.expression) { 487 | const type = checker.getTypeAtLocation(decl.expression); 488 | if (type && type.symbol && !map.has(type.symbol)) 489 | map.set(type.symbol, name); 490 | } 491 | } 492 | return map; 493 | }; 494 | 495 | let exposedType; 496 | const typeExports = new Map(); 497 | 498 | for (const exposedFile of exposedFiles) { 499 | const exposedSource = program.getSourceFile( 500 | path.resolve(rootPath, exposedFile), 501 | ); 502 | if (!exposedSource) { 503 | console.error( 504 | `No source found for ${exposedFile}. These files are available:`, 505 | ); 506 | for (const source of program.getSourceFiles()) { 507 | console.error(` - ${source.fileName}`); 508 | } 509 | continue; 510 | } 511 | 512 | const type = getTypeOfSourceFile(exposedSource); 513 | if (type) { 514 | if (!exposedType) { 515 | captureType(undefined, type, exposedSource); 516 | exposedType = type; 517 | } 518 | } 519 | 520 | for (const [name, type] of getTypeExportsOfSourceFile(exposedSource)) { 521 | captureType(undefined, type, exposedSource); 522 | typeExports.set(name, type); 523 | } 524 | } 525 | 526 | /** @typedef {{ name: string, optional: boolean, spread: boolean, documentation: string, type: ts.Type }} ParsedParameter */ 527 | /** @typedef {{ documentation: string, typeParameters?: readonly ts.Type[], args: ParsedParameter[], thisType: ts.Type, returnType: ts.Type }} ParsedSignature */ 528 | /** @typedef {string[]} SymbolName */ 529 | /** @typedef {Map} PropertiesMap */ 530 | 531 | /** @typedef {{ type: "primitive", name: string }} ParsedPrimitiveType */ 532 | /** @typedef {{ type: "typeParameter", name: string, constraint: ts.Type, defaultValue: ts.Type }} ParsedTypeParameterType */ 533 | /** @typedef {{ type: "tuple", typeArguments: readonly ts.Type[] }} ParsedTupleType */ 534 | /** @typedef {{ type: "interface", symbolName: SymbolName, subtype: "class" | "module" | "literal" | undefined, properties: PropertiesMap, constructors: ParsedSignature[], calls: ParsedSignature[], numberIndex?: ts.Type, stringIndex?: ts.Type, typeParameters?: readonly ts.Type[], baseTypes: readonly ts.Type[], documentation: string }} ParsedInterfaceType */ 535 | /** @typedef {{ type: "class" | "typeof class", symbolName: SymbolName, properties: PropertiesMap, staticProperties: PropertiesMap, constructors: ParsedSignature[], numberIndex?: ts.Type, stringIndex?: ts.Type, typeParameters?: readonly ts.Type[], baseType: ts.Type, correspondingType: ts.Type | undefined }} MergedClassType */ 536 | /** @typedef {{ type: "namespace", symbolName: SymbolName, calls: ParsedSignature[], exports: PropertiesMap }} MergedNamespaceType */ 537 | /** @typedef {{ type: "reference", target: ts.Type, typeArguments: readonly ts.Type[], typeArgumentsWithoutDefaults: readonly ts.Type[] }} ParsedReferenceType */ 538 | /** @typedef {{ type: "union", symbolName: SymbolName, types: ts.Type[], typeParameters?: readonly ts.Type[] }} ParsedUnionType */ 539 | /** @typedef {{ type: "intersection", symbolName: SymbolName, types: ts.Type[], typeParameters?: readonly ts.Type[] }} ParsedIntersectionType */ 540 | /** @typedef {{ type: "index", symbolName: SymbolName, objectType: ts.Type, indexType: ts.Type }} ParsedIndexType */ 541 | /** @typedef {{ type: "template", texts: readonly string[], types: readonly ts.Type[] }} ParsedTemplateType */ 542 | /** @typedef {{ type: "import", symbolName: SymbolName, exportName: string, from: string, isValue: boolean }} ParsedImportType */ 543 | /** @typedef {{ type: "symbol", symbolName: SymbolName }} ParsedSymbolType */ 544 | /** @typedef {ParsedPrimitiveType | ParsedTypeParameterType | ParsedTupleType | ParsedInterfaceType | ParsedReferenceType | ParsedUnionType | ParsedIntersectionType | ParsedIndexType | ParsedTemplateType | ParsedImportType | ParsedSymbolType} ParsedType */ 545 | /** @typedef {ParsedType | MergedClassType | MergedNamespaceType} MergedType */ 546 | 547 | const isExcluded = (name) => { 548 | return options.exclude.some((e) => e.test(name)); 549 | }; 550 | const isIncluded = (name) => { 551 | return options.include.some((e) => e.test(name)); 552 | }; 553 | 554 | /** 555 | * @param {ts.Type=} type the type 556 | * @param {string[]=} suffix additional suffix 557 | * @returns {string[]} name of the symbol 558 | */ 559 | const parseName = (type, suffix = []) => { 560 | if (type === exposedType) return ["exports"]; 561 | const symbol = /** @type {any} */ (type).symbol; 562 | const aliasSymbol = type.aliasSymbol; 563 | const getName = (symbol) => { 564 | if (!symbol) return undefined; 565 | if (symbol.getFlags() & ts.SymbolFlags.ValueModule) return sourceFile; 566 | const name = symbol.name; 567 | return name && 568 | name !== ts.InternalSymbolName.Type && 569 | name !== ts.InternalSymbolName.Object 570 | ? toIdentifier(name) 571 | : undefined; 572 | }; 573 | const valueDeclaration = 574 | getDeclaration(symbol) || getDeclaration(aliasSymbol) || undefined; 575 | const sourceFile = 576 | valueDeclaration && 577 | toIdentifier( 578 | valueDeclaration 579 | .getSourceFile() 580 | .fileName.slice(rootPath.length + 1) 581 | .replace(/^.*\/(?!index)/, "") 582 | .replace(/^lib\./, "") 583 | .replace(/\.(js|(d\.)?ts)$/, ""), 584 | ); 585 | const syntaxType = 586 | valueDeclaration && 587 | ({ 588 | [ts.SyntaxKind.ObjectLiteralExpression]: "Object", 589 | [ts.SyntaxKind.TypeLiteral]: "TypeLiteral", 590 | [ts.SyntaxKind.SourceFile]: "Module", 591 | [ts.SyntaxKind.JSDocTypedefTag]: "Object", 592 | [ts.SyntaxKind.JSDocTypeLiteral]: "Object", 593 | [ts.SyntaxKind.TypeAliasDeclaration]: "Alias", 594 | [ts.SyntaxKind.FunctionDeclaration]: "Function", 595 | [ts.SyntaxKind.FunctionExpression]: "Function", 596 | [ts.SyntaxKind.ArrowFunction]: "Function", 597 | [ts.SyntaxKind.FunctionType]: "Function", 598 | [ts.SyntaxKind.JSDocSignature]: "Function", 599 | [ts.SyntaxKind.JSDocCallbackTag]: "Function", 600 | [ts.SyntaxKind.MethodDeclaration]: "Method", 601 | [ts.SyntaxKind.MethodSignature]: "Method", 602 | [ts.SyntaxKind.EnumDeclaration]: "Enum", 603 | [ts.SyntaxKind.ClassDeclaration]: "Class", 604 | [ts.SyntaxKind.ClassExpression]: "Class", 605 | [ts.SyntaxKind.InterfaceDeclaration]: "Interface", 606 | }[valueDeclaration.kind] || 607 | `Kind${valueDeclaration.kind}_`); 608 | const name1 = getName(symbol); 609 | const name2 = getName(aliasSymbol); 610 | const result = [name1, name2, ...suffix, sourceFile, syntaxType]; 611 | if (!name1 && !name2) { 612 | result.unshift(AnonymousType); 613 | } 614 | return Array.from(new Set(result.filter((x) => x !== undefined))); 615 | }; 616 | 617 | /** 618 | * @param {ts.Symbol | ts.Signature | undefined} symbol symbol 619 | * @returns {string} documentation comment 620 | */ 621 | const getDocumentation = (symbol) => { 622 | if (!symbol) return ""; 623 | const comments = symbol.getDocumentationComment(checker); 624 | if (comments.length === 0) return ""; 625 | return `\n/**\n * ${comments 626 | .map((c) => c.text) 627 | .join("") 628 | .replace(/\n+/g, "\n * ")}\n */\n`; 629 | }; 630 | 631 | /** 632 | * @param {ts.Signature} signature signature 633 | * @returns {ParsedSignature} parsed signature 634 | */ 635 | const parseSignature = (signature) => { 636 | let canBeOptional = true; 637 | let rest = ts.signatureHasRestParameter(signature); 638 | /** 639 | * @param {ts.Symbol} p parameter 640 | * @returns {ParsedParameter} parsed 641 | */ 642 | const parseParameter = (p) => { 643 | const decl = getDeclaration(p); 644 | const paramDeclaration = decl && ts.isParameter(decl) ? decl : undefined; 645 | let jsdocParamDeclaration = 646 | decl && ts.isJSDocParameterTag(decl) ? decl : undefined; 647 | if (!jsdocParamDeclaration && decl) { 648 | const jsdoc = ts.getJSDocTags(decl); 649 | if ( 650 | jsdoc.length > 0 && 651 | jsdoc[0].kind === ts.SyntaxKind.JSDocParameterTag 652 | ) { 653 | jsdocParamDeclaration = /** @type {ts.JSDocParameterTag} */ ( 654 | jsdoc[0] 655 | ); 656 | } 657 | } 658 | const type = getTypeOfSymbol(p, false); 659 | const optional = 660 | canBeOptional && 661 | ((type.getFlags() & ts.TypeFlags.Any) !== 0 || 662 | (p.getFlags() & ts.SymbolFlags.Optional) !== 0 || 663 | (paramDeclaration && 664 | (!!paramDeclaration.initializer || 665 | !!paramDeclaration.questionToken)) || 666 | (jsdocParamDeclaration && 667 | jsdocParamDeclaration.typeExpression && 668 | jsdocParamDeclaration.typeExpression.type.kind === 669 | ts.SyntaxKind.JSDocOptionalType)); 670 | canBeOptional = canBeOptional && optional; 671 | const spread = rest; 672 | rest = false; 673 | const documentation = getDocumentation(p); 674 | return { 675 | name: p.name, 676 | optional, 677 | spread, 678 | documentation, 679 | type, 680 | }; 681 | }; 682 | const params = signature.getParameters().slice(); 683 | if ( 684 | rest && 685 | params.length > 0 && 686 | params[params.length - 1].name === "args" 687 | ) { 688 | const p = params[params.length - 1]; 689 | const type = getTypeOfSymbol(p, false); 690 | if (type.intrinsicName === "error") { 691 | // This fixes a problem that typescript adds `...args: any[]` to a signature when `arguments` as text is used in body 692 | params.pop(); 693 | rest = false; 694 | } 695 | } 696 | return { 697 | documentation: getDocumentation(signature), 698 | typeParameters: 699 | signature.typeParameters && signature.typeParameters.length > 0 700 | ? signature.typeParameters 701 | : undefined, 702 | args: params.reverse().map(parseParameter).reverse(), 703 | returnType: signature.getReturnType(), 704 | thisType: signature.thisParameter 705 | ? getTypeOfSymbol(signature.thisParameter, false) 706 | : undefined, 707 | }; 708 | }; 709 | 710 | const isNodeModulesSource = (sourceFile) => { 711 | return ( 712 | sourceFile.isDeclarationFile && 713 | sourceFile.fileName.slice(rootPath.length + 1).startsWith("node_modules/") 714 | ); 715 | }; 716 | const getRootPackage = (sourceFile) => { 717 | const match = /^(node_modules\/(@[^/]+\/)?[^/]+)/.exec( 718 | sourceFile.fileName.slice(rootPath.length + 1), 719 | ); 720 | if (!match) return undefined; 721 | const pkg = require(rootPath + "/" + match[1] + "/package.json"); 722 | const types = pkg.types || "index.d.ts"; 723 | return program.getSourceFile(rootPath + "/" + match[1] + "/" + types); 724 | }; 725 | 726 | const { typeMapping } = options; 727 | 728 | const isArrayBufferLike = (name) => { 729 | return [ 730 | "Uint8Array", 731 | "Uint8ClampedArray", 732 | "Uint16Array", 733 | "Uint32Array", 734 | "Int8Array", 735 | "Int16Array", 736 | "Int32Array", 737 | "BigUint64Array", 738 | "BigInt64Array", 739 | "Float16Array", 740 | "Float32Array", 741 | "Float64Array", 742 | "DataView", 743 | "Buffer", 744 | ].includes(name); 745 | }; 746 | 747 | /** 748 | * @param {ts.Type} type type 749 | * @returns {ParsedType | undefined} parsed type 750 | */ 751 | const parseType = (type) => { 752 | /** 753 | * @param {ts.Symbol[]} symbols list of symbols 754 | * @param {ts.Type[]=} baseTypes base types from which properties should be omitted 755 | * @param {SymbolName} symbolName name 756 | * @returns {PropertiesMap} map of types 757 | */ 758 | const toPropMap = (symbols, baseTypes = [], symbolName = []) => { 759 | /** @type {PropertiesMap} */ 760 | const properties = new Map(); 761 | for (const prop of symbols) { 762 | let name = prop.name; 763 | if (name === "prototype") continue; 764 | const nameForFilter = `${name} in ${symbolName.join(" ")}`; 765 | if (isExcluded(nameForFilter)) continue; 766 | if (!isIncluded(nameForFilter)) { 767 | if (name.startsWith("_") && !name.startsWith("__")) continue; 768 | } 769 | if ( 770 | name.startsWith("__@") && 771 | (prop.flags & ts.TypeFlags.ESSymbolLike) === 0 772 | ) 773 | continue; 774 | if (baseTypes.some((t) => t.getProperty(name))) continue; 775 | let modifierFlags; 776 | let innerType = getTypeOfSymbol(prop, true); 777 | const decl = getDeclaration(prop); 778 | if (decl) { 779 | modifierFlags = ts.getCombinedModifierFlags(decl); 780 | } 781 | if (!innerType) continue; 782 | if (modifierFlags & ts.ModifierFlags.Private) { 783 | continue; 784 | } 785 | const flags = prop.getFlags(); 786 | if (name.startsWith("__@")) { 787 | name = `[Symbol.${name.slice(3, name.lastIndexOf("@"))}]`; 788 | } else if (!/^[_a-zA-Z$][_a-zA-Z$0-9]*$/.test(name)) { 789 | name = JSON.stringify(name); 790 | } 791 | 792 | let needContinue = false; 793 | 794 | for (const [regExp, newName] of Object.entries(typeMapping)) { 795 | if (new RegExp(regExp).test(nameForFilter)) { 796 | const newType = checker.getESSymbolType(); 797 | /** @type {ts.LiteralType} */ 798 | (newType).intrinsicName = newName; 799 | properties.set(name, { 800 | type: newType, 801 | method: (flags & ts.SymbolFlags.Method) !== 0, 802 | optional: (flags & ts.SymbolFlags.Optional) !== 0, 803 | readonly: (modifierFlags & ts.ModifierFlags.Readonly) !== 0, 804 | getter: 805 | (flags & ts.SymbolFlags.GetAccessor) !== 0 && 806 | (flags & ts.SymbolFlags.SetAccessor) === 0, 807 | documentation: getDocumentation(prop), 808 | }); 809 | needContinue = true; 810 | break; 811 | } 812 | } 813 | 814 | if (needContinue) { 815 | continue; 816 | } 817 | 818 | properties.set(name, { 819 | type: innerType, 820 | method: (flags & ts.SymbolFlags.Method) !== 0, 821 | optional: (flags & ts.SymbolFlags.Optional) !== 0, 822 | readonly: (modifierFlags & ts.ModifierFlags.Readonly) !== 0, 823 | getter: 824 | (flags & ts.SymbolFlags.GetAccessor) !== 0 && 825 | (flags & ts.SymbolFlags.SetAccessor) === 0, 826 | documentation: getDocumentation(prop), 827 | }); 828 | } 829 | return properties; 830 | }; 831 | 832 | /** 833 | * 834 | * @param {readonly ts.Type[]} args 835 | * @param {readonly ts.Type[] | undefined} parameters 836 | * @returns {readonly ts.Type[]} 837 | */ 838 | const omitDefaults = (args, parameters) => { 839 | if (!parameters) return args; 840 | const argsWithoutDefaults = args.slice(); 841 | for (let i = args.length - 1; i > 0; i--) { 842 | if (!parameters[i] || args[i] === parameters[i].getDefault()) { 843 | argsWithoutDefaults.length--; 844 | } else { 845 | break; 846 | } 847 | } 848 | return argsWithoutDefaults; 849 | }; 850 | 851 | if (/** @type {any} */ (type).isTypeParameter()) { 852 | return { 853 | type: "typeParameter", 854 | name: type.symbol.name, 855 | constraint: type.getConstraint(), 856 | defaultValue: type.getDefault(), 857 | }; 858 | } 859 | 860 | /** @type {ts.TypeNode} */ 861 | let typeNode = /** @type {any} */ (type)._typeNode; 862 | if (type.aliasSymbol) { 863 | const fullEscapedName = getFullEscapedName(type.aliasSymbol); 864 | if (fullEscapedName.includes("NodeJS.")) { 865 | return { 866 | type: "primitive", 867 | name: fullEscapedName, 868 | }; 869 | } 870 | const aliasType = checker.getDeclaredTypeOfSymbol(type.aliasSymbol); 871 | if (aliasType && aliasType !== type) { 872 | const typeArguments = type.aliasTypeArguments || []; 873 | return { 874 | type: "reference", 875 | target: aliasType, 876 | typeArguments, 877 | typeArgumentsWithoutDefaults: omitDefaults( 878 | typeArguments, 879 | aliasType.isClassOrInterface() && aliasType.typeParameters, 880 | ), 881 | }; 882 | } 883 | } 884 | 885 | if (typeNode && !isNodeModulesSource(typeNode.getSourceFile())) { 886 | switch (typeNode.kind) { 887 | case ts.SyntaxKind.IndexedAccessType: { 888 | const { objectType, indexType } = 889 | /** @type {ts.IndexedAccessTypeNode} */ (typeNode); 890 | const objectTypeType = checker.getTypeAtLocation(objectType); 891 | /** @type {any} */ (objectTypeType)._typeNode = objectType; 892 | const indexTypeType = checker.getTypeAtLocation(indexType); 893 | /** @type {any} */ (indexTypeType)._typeNode = indexType; 894 | if (objectTypeType && indexTypeType) { 895 | return { 896 | type: "index", 897 | symbolName: parseName(type), 898 | objectType: objectTypeType, 899 | indexType: indexTypeType, 900 | }; 901 | } 902 | break; 903 | } 904 | case ts.SyntaxKind.TypeReference: { 905 | const { typeArguments, typeName } = 906 | /** @type {ts.TypeReferenceNode} */ (typeNode); 907 | const typeArgumentsTypes = typeArguments 908 | ? typeArguments.map((node) => { 909 | const type = checker.getTypeAtLocation(node); 910 | if (type) { 911 | /** @type {any} */ (type)._typeNode = node; 912 | } 913 | return type; 914 | }) 915 | : []; 916 | const targetSymbol = checker.getSymbolAtLocation(typeName); 917 | const targetType = getTypeOfSymbol(targetSymbol, false); 918 | if ( 919 | typeArgumentsTypes.every(Boolean) && 920 | targetType && 921 | targetType !== type 922 | ) { 923 | return { 924 | type: "reference", 925 | target: targetType, 926 | typeArguments: typeArgumentsTypes, 927 | typeArgumentsWithoutDefaults: omitDefaults( 928 | typeArgumentsTypes, 929 | targetType.isClassOrInterface() && targetType.typeParameters, 930 | ), 931 | }; 932 | } 933 | break; 934 | } 935 | } 936 | } 937 | 938 | if (type.isUnion()) { 939 | let types = type.types; 940 | 941 | const origin = 942 | /** @type {ts.UnionType & { origin: ts.UnionType } | undefined} */ 943 | (type).origin; 944 | const nodeJSOriginTypes = 945 | origin && 946 | origin.types && 947 | origin.types.filter((item) => { 948 | if (!item.aliasSymbol) { 949 | return false; 950 | } 951 | 952 | const fullEscapedName = getFullEscapedName(item.aliasSymbol); 953 | 954 | if (fullEscapedName.includes("NodeJS.")) { 955 | return true; 956 | } 957 | }); 958 | 959 | if (nodeJSOriginTypes && nodeJSOriginTypes.length > 0) { 960 | types = types.filter((item) => { 961 | if (!item.symbol) { 962 | return true; 963 | } 964 | 965 | return !isArrayBufferLike(getFullEscapedName(item.symbol)); 966 | }); 967 | 968 | types.push(...nodeJSOriginTypes); 969 | } 970 | 971 | return { 972 | type: "union", 973 | symbolName: parseName(type), 974 | types, 975 | typeParameters: 976 | type.aliasTypeArguments && type.aliasTypeArguments.length > 0 977 | ? type.aliasTypeArguments 978 | : undefined, 979 | }; 980 | } 981 | 982 | if (type.isIntersection()) { 983 | return { 984 | type: "intersection", 985 | symbolName: parseName(type), 986 | types: type.types, 987 | typeParameters: 988 | type.aliasTypeArguments && type.aliasTypeArguments.length > 0 989 | ? type.aliasTypeArguments 990 | : undefined, 991 | }; 992 | } 993 | 994 | if (type.intrinsicName) { 995 | return { 996 | type: "primitive", 997 | name: type.intrinsicName === "error" ? "any" : type.intrinsicName, 998 | }; 999 | } 1000 | 1001 | const flags = type.getFlags(); 1002 | 1003 | if (flags & ts.TypeFlags.Literal) 1004 | return { type: "primitive", name: checker.typeToString(type) }; 1005 | if (flags & ts.TypeFlags.TemplateLiteral) { 1006 | if (!templateLiterals) return { type: "primitive", name: "string" }; 1007 | const templateType = /** @type {ts.TemplateLiteralType} */ (type); 1008 | return { 1009 | type: "template", 1010 | texts: templateType.texts, 1011 | types: templateType.types, 1012 | }; 1013 | } 1014 | if (flags & ts.TypeFlags.Any) return { type: "primitive", name: "any" }; 1015 | if (flags & ts.TypeFlags.Unknown) 1016 | return { type: "primitive", name: "unknown" }; 1017 | if (flags & ts.TypeFlags.String) 1018 | return { type: "primitive", name: "string" }; 1019 | if (flags & ts.TypeFlags.Number) 1020 | return { type: "primitive", name: "number" }; 1021 | if (flags & ts.TypeFlags.Boolean) 1022 | return { type: "primitive", name: "boolean" }; 1023 | if (flags & ts.TypeFlags.BigInt) 1024 | return { type: "primitive", name: "bigint" }; 1025 | if (flags & ts.TypeFlags.Void) return { type: "primitive", name: "void" }; 1026 | if (flags & ts.TypeFlags.Undefined) 1027 | return { type: "primitive", name: "undefined" }; 1028 | if (flags & ts.TypeFlags.Null) return { type: "primitive", name: "null" }; 1029 | if (flags & ts.TypeFlags.Never) return { type: "primitive", name: "never" }; 1030 | if (flags & ts.TypeFlags.Void) return { type: "primitive", name: "void" }; 1031 | 1032 | const objectFlags = 1033 | flags & ts.TypeFlags.Object 1034 | ? /** @type {ts.ObjectType} */ (type).objectFlags 1035 | : 0; 1036 | if (objectFlags & ts.ObjectFlags.JSLiteral) { 1037 | const props = type.getProperties(); 1038 | if (props.length === 0) { 1039 | return { 1040 | type: "primitive", 1041 | name: "object", 1042 | }; 1043 | } 1044 | return { 1045 | type: "interface", 1046 | symbolName: [AnonymousType, "Literal"], 1047 | subtype: "literal", 1048 | properties: toPropMap(props, undefined, ["Literal"]), 1049 | constructors: [], 1050 | calls: [], 1051 | baseTypes: [], 1052 | documentation: getDocumentation(type.getSymbol()), 1053 | }; 1054 | } 1055 | 1056 | const symbol = type.aliasSymbol || type.getSymbol(); 1057 | 1058 | if (objectFlags & ts.ObjectFlags.Reference) { 1059 | const typeRef = /** @type {ts.TypeReference} */ (type); 1060 | const typeArguments = checker.getTypeArguments(typeRef); 1061 | if (objectFlags & ts.ObjectFlags.Tuple) { 1062 | const tupleType = /** @type {ts.TupleType} */ (type); 1063 | return { 1064 | type: "primitive", 1065 | name: tupleType.hasRestElement ? "[...]" : "[]", 1066 | }; 1067 | } 1068 | if (typeRef !== typeRef.target) { 1069 | return { 1070 | type: "reference", 1071 | target: typeRef.target, 1072 | typeArguments, 1073 | typeArgumentsWithoutDefaults: omitDefaults( 1074 | typeArguments, 1075 | typeRef.target.typeParameters, 1076 | ), 1077 | }; 1078 | } 1079 | } 1080 | 1081 | if (symbol) { 1082 | // Handle `NodeJS` prefix for global types 1083 | if (symbol.escapedName !== undefined) { 1084 | const fullEscapedName = getFullEscapedName(symbol); 1085 | 1086 | if (fullEscapedName.includes("NodeJS.")) { 1087 | return { 1088 | type: "primitive", 1089 | name: fullEscapedName, 1090 | }; 1091 | } 1092 | } 1093 | 1094 | const decl = getDeclaration(symbol); 1095 | if (decl && isNodeModulesSource(decl.getSourceFile())) { 1096 | let symbol = /** @type {any} */ (decl).symbol; 1097 | const isValue = !!( 1098 | symbol && 1099 | symbol.valueDeclaration && 1100 | (symbol.flags & ts.SymbolFlags.Function || 1101 | symbol.flags & ts.SymbolFlags.Class) 1102 | ); 1103 | const potentialSources = [ 1104 | getRootPackage(decl.getSourceFile()), 1105 | decl.getSourceFile(), 1106 | ].filter(Boolean); 1107 | let externalSource; 1108 | let exportName; 1109 | outer: for (const source of potentialSources) { 1110 | externalSource = source; 1111 | const symbolToExport = getExportsOfSourceFile(externalSource); 1112 | exportName = symbolToExport.get(symbol); 1113 | if (exportName) break; 1114 | for (const [key, name] of symbolToExport) { 1115 | if (getTypeOfSymbol(key, false) === type) { 1116 | symbol = key; 1117 | exportName = name; 1118 | break outer; 1119 | } 1120 | } 1121 | } 1122 | if (exportName === undefined) { 1123 | if (verbose) { 1124 | console.log( 1125 | `${parseName(type).join( 1126 | " ", 1127 | )} is an imported symbol, but couldn't find export in ${potentialSources.map( 1128 | (source) => { 1129 | const symbolToExport = getExportsOfSourceFile(source); 1130 | return `${source.fileName} (exports: ${[ 1131 | ...symbolToExport.values(), 1132 | ] 1133 | .sort() 1134 | .join(", ")})`; 1135 | }, 1136 | )}`, 1137 | ); 1138 | } 1139 | } else { 1140 | if (isSourceFileModule(externalSource)) { 1141 | const match = 1142 | /^(.+\/node_modules\/(?:@types\/)?)((?:@[^/]+\/)?[^/]+)(.*?)(\.d\.ts)?$/.exec( 1143 | externalSource.fileName, 1144 | ); 1145 | if (!match) { 1146 | console.error( 1147 | `${externalSource.fileName} doesn't match node_modules import schema`, 1148 | ); 1149 | } else { 1150 | let from = match[2] + match[3]; 1151 | try { 1152 | const pkg = require(match[1] + match[2] + "/package.json"); 1153 | const regExp = new RegExp( 1154 | "^(\\.\\/)?" + quoteMeta(match[3].slice(1)) + "(\\.d\\.ts)?$", 1155 | ); 1156 | const types = pkg.types || "index.d.ts"; 1157 | if (regExp.test(types)) { 1158 | from = match[2]; 1159 | } 1160 | } catch (e) { 1161 | // sorry, doesn't work 1162 | } 1163 | return { 1164 | type: "import", 1165 | symbolName: parseName(type, [ 1166 | toIdentifier(exportName), 1167 | "Import", 1168 | ]), 1169 | exportName, 1170 | from, 1171 | isValue, 1172 | }; 1173 | } 1174 | } else { 1175 | const match = /"(.+?)"\.?(.*)$/.exec(exportName); 1176 | if (match) { 1177 | return { 1178 | type: "import", 1179 | symbolName: parseName(type, [toIdentifier(match[2]), "Import"]), 1180 | exportName: match[2], 1181 | from: match[1], 1182 | isValue, 1183 | }; 1184 | } 1185 | return { 1186 | type: "primitive", 1187 | name: exportName, 1188 | }; 1189 | } 1190 | } 1191 | } 1192 | } 1193 | 1194 | const symbolName = parseName(type); 1195 | 1196 | if (flags & ts.TypeFlags.UniqueESSymbol) { 1197 | return { 1198 | type: "symbol", 1199 | symbolName, 1200 | }; 1201 | } 1202 | 1203 | const declaration = getDeclaration(symbol); 1204 | 1205 | return { 1206 | type: "interface", 1207 | symbolName, 1208 | subtype: type.isClass() 1209 | ? "class" 1210 | : declaration && declaration.kind === ts.SyntaxKind.SourceFile 1211 | ? "module" 1212 | : undefined, 1213 | properties: toPropMap( 1214 | type.getProperties(), 1215 | type.getBaseTypes(), 1216 | symbolName, 1217 | ), 1218 | constructors: type.getConstructSignatures().map(parseSignature), 1219 | calls: type.getCallSignatures().map(parseSignature), 1220 | numberIndex: type.getNumberIndexType(), 1221 | stringIndex: type.getStringIndexType(), 1222 | baseTypes: type.getBaseTypes() || [], 1223 | typeParameters: 1224 | type.isClassOrInterface() && 1225 | type.typeParameters && 1226 | type.typeParameters.length > 0 1227 | ? type.typeParameters 1228 | : type.aliasSymbol && 1229 | type.aliasTypeArguments && 1230 | type.aliasTypeArguments.length > 0 1231 | ? type.aliasTypeArguments 1232 | : undefined, 1233 | documentation: getDocumentation(type.getSymbol()), 1234 | }; 1235 | }; 1236 | 1237 | /** @type {Set} */ 1238 | const typeUsedAsArgument = new Set(); 1239 | /** @type {Set} */ 1240 | const typeUsedAsReturnValue = new Set(); 1241 | /** @type {Set} */ 1242 | const typeUsedAsConstructedValue = new Set(); 1243 | /** @type {Set} */ 1244 | const typeUsedAsBaseType = new Set(); 1245 | /** @type {Set} */ 1246 | const typeUsedAsTypeArgument = new Set(); 1247 | /** @type {Set} */ 1248 | const typeUsedInUnion = new Set(); 1249 | 1250 | /** @type {Map} */ 1251 | const parsedCollectedTypes = new Map(); 1252 | 1253 | for (const type of collectedTypes) { 1254 | const parsed = parseType(type); 1255 | if (!parsed) { 1256 | console.error(checker.typeToString(type), "can't be parsed"); 1257 | continue; 1258 | } 1259 | parsedCollectedTypes.set(type, parsed); 1260 | switch (parsed.type) { 1261 | case "typeParameter": 1262 | if (parsed.constraint) captureType(type, parsed.constraint); 1263 | if (parsed.defaultValue) captureType(type, parsed.defaultValue); 1264 | break; 1265 | case "template": 1266 | for (const inner of parsed.types) captureType(type, inner); 1267 | break; 1268 | case "union": 1269 | for (const inner of parsed.types) { 1270 | typeUsedInUnion.add(inner); 1271 | captureType(type, inner); 1272 | } 1273 | if (parsed.typeParameters) 1274 | for (const prop of parsed.typeParameters) captureType(type, prop); 1275 | break; 1276 | case "intersection": 1277 | for (const inner of parsed.types) captureType(type, inner); 1278 | if (parsed.typeParameters) 1279 | for (const prop of parsed.typeParameters) captureType(type, prop); 1280 | break; 1281 | case "index": 1282 | captureType(type, parsed.objectType); 1283 | captureType(type, parsed.indexType); 1284 | break; 1285 | case "reference": 1286 | captureType(type, parsed.target); 1287 | for (const inner of parsed.typeArgumentsWithoutDefaults) { 1288 | typeUsedAsTypeArgument.add(inner); 1289 | captureType(type, inner); 1290 | } 1291 | break; 1292 | case "interface": 1293 | for (const prop of parsed.baseTypes) { 1294 | typeUsedAsBaseType.add(prop); 1295 | captureType(type, prop); 1296 | } 1297 | for (const prop of parsed.properties.values()) { 1298 | captureType(type, /** @type {ts.Type} */ (prop.type)); 1299 | } 1300 | for (const call of parsed.calls) { 1301 | for (const arg of call.args) { 1302 | typeUsedAsArgument.add(arg.type); 1303 | captureType(type, arg.type); 1304 | } 1305 | typeUsedAsReturnValue.add(call.returnType); 1306 | captureType(type, call.returnType); 1307 | if (call.thisType) { 1308 | typeUsedAsArgument.add(call.thisType); 1309 | captureType(type, call.thisType); 1310 | } 1311 | } 1312 | for (const construct of parsed.constructors) { 1313 | for (const arg of construct.args) { 1314 | typeUsedAsArgument.add(arg.type); 1315 | captureType(type, arg.type); 1316 | } 1317 | typeUsedAsConstructedValue.add(construct.returnType); 1318 | captureType(type, construct.returnType); 1319 | } 1320 | if (parsed.numberIndex) captureType(type, parsed.numberIndex); 1321 | if (parsed.stringIndex) captureType(type, parsed.stringIndex); 1322 | if (parsed.typeParameters) 1323 | for (const prop of parsed.typeParameters) captureType(type, prop); 1324 | break; 1325 | } 1326 | } 1327 | 1328 | const isSimpleFunction = (parsed) => { 1329 | return ( 1330 | parsed.type === "interface" && 1331 | parsed.properties.size === 0 && 1332 | parsed.constructors.length === 0 && 1333 | parsed.calls.length === 1 && 1334 | (!parsed.typeParameters || parsed.typeParameters.length === 0) 1335 | ); 1336 | }; 1337 | 1338 | /// Convert interfaces to classes /// 1339 | /** 1340 | * @param {ts.Type} type the type 1341 | * @param {ParsedType | MergedType} parsed the parsed variant 1342 | * @returns {boolean} true, when it can be classified 1343 | */ 1344 | const canBeClassified = (type, parsed = parsedCollectedTypes.get(type)) => { 1345 | if (parsed === undefined) return false; 1346 | if (parsed.type === "reference") return true; 1347 | if (parsed.type === "class") return true; 1348 | if ( 1349 | parsed.type !== "interface" || 1350 | parsed.calls.length !== 0 || 1351 | parsed.constructors.length !== 0 || 1352 | parsed.baseTypes.length > 1 1353 | ) { 1354 | return false; 1355 | } 1356 | if (parsed.baseTypes.length === 0) return true; 1357 | return parsed.baseTypes.length === 1 && canBeBaseClass(parsed.baseTypes[0]); 1358 | }; 1359 | 1360 | /** 1361 | * @param {ts.Type} type the type 1362 | * @returns {boolean} true, when it can be a base class 1363 | */ 1364 | const canBeBaseClass = (type) => { 1365 | const baseType = type; 1366 | const baseParsed = parsedCollectedTypes.get(baseType); 1367 | return ( 1368 | baseParsed.type === "primitive" || canBeClassified(baseType, baseParsed) 1369 | ); 1370 | }; 1371 | 1372 | const toBeClassified = new Set(); 1373 | 1374 | for (const [type, parsed] of parsedCollectedTypes) { 1375 | if ( 1376 | parsed.type === "interface" && 1377 | parsed.constructors.length > 0 && 1378 | !parsed.typeParameters && 1379 | parsed.calls.length === 0 && 1380 | !parsed.numberIndex && 1381 | !parsed.stringIndex && 1382 | parsed.baseTypes.length === 0 1383 | ) { 1384 | const instanceType = parsed.constructors[0].returnType; 1385 | if (parsed.constructors.every((c) => c.returnType === instanceType)) { 1386 | const instance = parsedCollectedTypes.get(instanceType); 1387 | if ( 1388 | instance !== undefined && 1389 | instance.type === "interface" && 1390 | instance.calls.length === 0 && 1391 | instance.constructors.length === 0 && 1392 | ((instance.baseTypes.length === 1 && 1393 | canBeBaseClass(instance.baseTypes[0])) || 1394 | instance.baseTypes.length === 0) 1395 | ) { 1396 | /** @type {Omit} */ 1397 | const merged = { 1398 | symbolName: instance.symbolName, 1399 | properties: instance.properties, 1400 | staticProperties: parsed.properties, 1401 | constructors: parsed.constructors, 1402 | numberIndex: instance.numberIndex, 1403 | stringIndex: instance.stringIndex, 1404 | typeParameters: instance.typeParameters, 1405 | baseType: instance.baseTypes[0], 1406 | }; 1407 | parsedCollectedTypes.set(instanceType, { 1408 | type: "class", 1409 | correspondingType: type, 1410 | ...merged, 1411 | }); 1412 | parsedCollectedTypes.set(type, { 1413 | type: "typeof class", 1414 | correspondingType: instanceType, 1415 | ...merged, 1416 | }); 1417 | if (merged.baseType) toBeClassified.add(merged.baseType); 1418 | } 1419 | } 1420 | } else if (parsed.type === "interface" && parsed.subtype === "class") { 1421 | if (canBeClassified(type)) { 1422 | toBeClassified.add(type); 1423 | } else if (verbose) { 1424 | console.log( 1425 | `${checker.typeToString( 1426 | type, 1427 | )} was a class in source code, but we are unable to generate a class for it.`, 1428 | ); 1429 | } 1430 | } 1431 | } 1432 | 1433 | for (const type of toBeClassified) { 1434 | const parsed = parsedCollectedTypes.get(type); 1435 | if (!parsed) continue; 1436 | if (parsed.type !== "interface") continue; 1437 | /** @type {MergedClassType} */ 1438 | const newParsed = { 1439 | type: "class", 1440 | correspondingType: undefined, 1441 | symbolName: parsed.symbolName, 1442 | properties: parsed.properties, 1443 | staticProperties: new Map(), 1444 | constructors: [], 1445 | numberIndex: parsed.numberIndex, 1446 | stringIndex: parsed.stringIndex, 1447 | typeParameters: parsed.typeParameters, 1448 | baseType: parsed.baseTypes[0], 1449 | }; 1450 | parsedCollectedTypes.set(type, newParsed); 1451 | } 1452 | 1453 | /// Analyse unions and intersections /// 1454 | 1455 | for (const [type, parsed] of parsedCollectedTypes) { 1456 | if (parsed.type === "union" || parsed.type === "intersection") { 1457 | if (typeUsedAsTypeArgument.has(type)) { 1458 | for (const t of parsed.types) { 1459 | typeUsedAsTypeArgument.add(t); 1460 | } 1461 | } 1462 | if (typeUsedAsReturnValue.has(type)) { 1463 | for (const t of parsed.types) { 1464 | typeUsedAsReturnValue.add(t); 1465 | } 1466 | } 1467 | if (typeUsedAsConstructedValue.has(type)) { 1468 | for (const t of parsed.types) { 1469 | typeUsedAsConstructedValue.add(t); 1470 | } 1471 | } 1472 | } 1473 | if (parsed.type === "intersection") { 1474 | const keys = new Set(); 1475 | /** @type {ParsedInterfaceType[]} */ 1476 | const subtypes = []; 1477 | if ( 1478 | parsed.types.every((type) => { 1479 | const parsed = parsedCollectedTypes.get(type); 1480 | if (parsed.type !== "interface") return false; 1481 | subtypes.push(parsed); 1482 | return ( 1483 | (typeReferencedBy.get(type).size === 1 || 1484 | parsed.symbolName[0] === AnonymousType || 1485 | parsed.properties.size === 0) && 1486 | !parsed.subtype && 1487 | !parsed.typeParameters && 1488 | !parsed.stringIndex && 1489 | !parsed.numberIndex && 1490 | parsed.baseTypes.length === 0 && 1491 | Array.from(parsed.properties.keys()).every((key) => { 1492 | if (keys.has(key)) return false; 1493 | keys.add(key); 1494 | return true; 1495 | }) 1496 | ); 1497 | }) 1498 | ) { 1499 | const interfaceSubtypes = subtypes; 1500 | const symbol = /** @type {any} */ (type).symbol; 1501 | const declaration = symbol && getDeclaration(symbol); 1502 | /** @type {ParsedInterfaceType} */ 1503 | const newParsed = { 1504 | type: "interface", 1505 | subtype: 1506 | declaration && declaration.kind === ts.SyntaxKind.SourceFile 1507 | ? "module" 1508 | : undefined, 1509 | symbolName: parsed.symbolName, 1510 | baseTypes: [], 1511 | calls: flatten(interfaceSubtypes.map((p) => p.calls)), 1512 | constructors: flatten(interfaceSubtypes.map((p) => p.constructors)), 1513 | properties: new Map( 1514 | flatten(interfaceSubtypes.map((p) => p.properties)), 1515 | ), 1516 | documentation: interfaceSubtypes 1517 | .map((p) => p.documentation) 1518 | .join("\n") 1519 | .replace(/\n+/g, "\n"), 1520 | }; 1521 | parsedCollectedTypes.set(type, newParsed); 1522 | } 1523 | } 1524 | } 1525 | 1526 | /// Convert interfaces to namespaces /// 1527 | 1528 | for (const [type, parsed] of parsedCollectedTypes) { 1529 | if (parsed.type !== "interface") continue; 1530 | if ( 1531 | parsed.numberIndex || 1532 | parsed.stringIndex || 1533 | parsed.baseTypes.length > 0 || 1534 | parsed.typeParameters || 1535 | parsed.constructors.length > 0 || 1536 | parsed.properties.size === 0 || 1537 | typeUsedAsBaseType.has(type) || 1538 | typeUsedAsConstructedValue.has(type) || 1539 | typeUsedAsArgument.has(type) || 1540 | typeUsedAsTypeArgument.has(type) 1541 | ) { 1542 | continue; 1543 | } 1544 | if ( 1545 | parsed.subtype !== undefined && 1546 | parsed.subtype !== "module" && 1547 | parsed.subtype !== "literal" && 1548 | exposedType !== type 1549 | ) { 1550 | continue; 1551 | } 1552 | if (exposedType !== type && parsed.subtype !== "module") { 1553 | if (typeUsedAsReturnValue.has(type)) continue; 1554 | // heuristic: only referenced from other namespaces 1555 | if ( 1556 | !Array.from(typeReferencedBy.get(type) || [], (t) => 1557 | parsedCollectedTypes.get(t), 1558 | ).every((p) => p.type === "namespace") 1559 | ) { 1560 | continue; 1561 | } 1562 | } 1563 | /** @type {MergedNamespaceType} */ 1564 | const newParsed = { 1565 | type: "namespace", 1566 | symbolName: parsed.symbolName, 1567 | calls: parsed.calls, 1568 | exports: parsed.properties, 1569 | }; 1570 | parsedCollectedTypes.set(type, newParsed); 1571 | } 1572 | 1573 | /// Merge identical types /// 1574 | 1575 | /** 1576 | * @param {string} prefix type parameter prefix 1577 | * @param {ParsedSignature} signature signature 1578 | * @param {number} index signature index 1579 | * @returns {any[] | undefined} hash 1580 | */ 1581 | const getSigHash = (prefix, signature, index) => { 1582 | const { args, returnType, typeParameters, documentation } = signature; 1583 | const typeParametersMap = new Map( 1584 | typeParameters && typeParameters.map((t, i) => [t, i]), 1585 | ); 1586 | return [ 1587 | "args", 1588 | ...flatten( 1589 | args.map((arg) => [ 1590 | arg.name, 1591 | arg.optional, 1592 | arg.spread, 1593 | arg.type, 1594 | arg.documentation, 1595 | ]), 1596 | ), 1597 | "return", 1598 | returnType, 1599 | "typeParameters", 1600 | typeParameters ? typeParameters.length : 0, 1601 | "documentation", 1602 | documentation, 1603 | ].map((item) => { 1604 | const x = typeParametersMap.get(item); 1605 | return x === undefined ? item : `${prefix}${index}_${x}`; 1606 | }); 1607 | }; 1608 | 1609 | /** 1610 | * @param {MergedType} parsed type 1611 | * @returns {any[] | undefined} hash 1612 | */ 1613 | const getTypeHash = (parsed) => { 1614 | switch (parsed.type) { 1615 | case "primitive": { 1616 | return [parsed.type, parsed.name]; 1617 | } 1618 | case "typeParameter": { 1619 | return [ 1620 | parsed.type, 1621 | parsed.name, 1622 | parsed.constraint, 1623 | parsed.defaultValue, 1624 | ]; 1625 | } 1626 | case "template": { 1627 | return [parsed.type, ...parsed.texts, ...parsed.types]; 1628 | } 1629 | case "reference": { 1630 | const { target, typeArgumentsWithoutDefaults } = parsed; 1631 | if (typeArgumentsWithoutDefaults.length === 0) return undefined; 1632 | return [parsed.type, target, ...typeArgumentsWithoutDefaults]; 1633 | } 1634 | case "index": { 1635 | const { objectType, indexType } = parsed; 1636 | return [parsed.type, objectType, indexType]; 1637 | } 1638 | case "union": 1639 | case "intersection": { 1640 | const { symbolName, types, typeParameters } = parsed; 1641 | const typeParametersMap = new Map( 1642 | typeParameters && typeParameters.map((t, i) => [t, i]), 1643 | ); 1644 | return [ 1645 | parsed.type, 1646 | symbolName[0], 1647 | typeParameters ? typeParameters.length : 0, 1648 | ...types, 1649 | ].map((item) => { 1650 | const x = typeParametersMap.get(/** @type {ts.Type} */ (item)); 1651 | return x === undefined ? item : x; 1652 | }); 1653 | } 1654 | case "interface": { 1655 | const { 1656 | symbolName, 1657 | baseTypes, 1658 | calls, 1659 | constructors, 1660 | properties, 1661 | numberIndex, 1662 | stringIndex, 1663 | typeParameters, 1664 | documentation, 1665 | } = parsed; 1666 | if ( 1667 | calls.length === 0 && 1668 | constructors.length === 0 && 1669 | properties.size === 0 && 1670 | !numberIndex && 1671 | !stringIndex 1672 | ) { 1673 | // need to have something unique 1674 | return undefined; 1675 | } 1676 | const callHashes = calls.map(getSigHash.bind(null, "call")); 1677 | if (callHashes.some((x) => !x)) return undefined; 1678 | const constructorHashes = constructors.map( 1679 | getSigHash.bind(null, "constructor"), 1680 | ); 1681 | if (constructorHashes.some((x) => !x)) return undefined; 1682 | const typeParametersMap = new Map( 1683 | typeParameters && typeParameters.map((t, i) => [t, i]), 1684 | ); 1685 | return [ 1686 | parsed.type, 1687 | symbolName[0], 1688 | "base", 1689 | ...baseTypes, 1690 | "calls", 1691 | ...flatten(callHashes), 1692 | "constructors", 1693 | ...flatten(constructorHashes), 1694 | "properties", 1695 | ...flatten( 1696 | Array.from(properties, ([name, { type, optional }]) => [ 1697 | name, 1698 | type, 1699 | optional, 1700 | ]), 1701 | ), 1702 | "numberIndex", 1703 | numberIndex, 1704 | "stringIndex", 1705 | stringIndex, 1706 | "typeParameters", 1707 | typeParameters ? typeParameters.length : 0, 1708 | "documentation", 1709 | documentation, 1710 | ].map((item) => { 1711 | const x = typeParametersMap.get(item); 1712 | return x === undefined ? item : x; 1713 | }); 1714 | } 1715 | } 1716 | return undefined; 1717 | }; 1718 | 1719 | const knownTypes = new TupleMap(); 1720 | let updates = true; 1721 | while (updates) { 1722 | updates = false; 1723 | for (const [type, parsed] of parsedCollectedTypes) { 1724 | const hash = getTypeHash(parsed); 1725 | if (!hash) continue; 1726 | const mappedHash = hash.map((item) => { 1727 | let parsed = parsedCollectedTypes.get(item); 1728 | if (!parsed) return item; 1729 | while ( 1730 | parsed.type === "reference" && 1731 | parsed.typeArgumentsWithoutDefaults.length === 0 1732 | ) { 1733 | parsed = parsedCollectedTypes.get(parsed.target); 1734 | } 1735 | return parsed; 1736 | }); 1737 | const otherType = knownTypes.get(mappedHash); 1738 | if (otherType && otherType !== type) { 1739 | const otherParsed = parsedCollectedTypes.get(otherType); 1740 | if (otherParsed === parsed) continue; 1741 | if ("symbolName" in otherParsed && "symbolName" in parsed) { 1742 | const commonSymbolName = otherParsed.symbolName.filter((n) => 1743 | parsed.symbolName.includes(n), 1744 | ); 1745 | otherParsed.symbolName = commonSymbolName; 1746 | } 1747 | parsedCollectedTypes.set( 1748 | type, 1749 | otherParsed.type === "primitive" || otherParsed.type === "reference" 1750 | ? otherParsed 1751 | : { 1752 | type: "reference", 1753 | target: otherType, 1754 | typeArguments: [], 1755 | typeArgumentsWithoutDefaults: [], 1756 | }, 1757 | ); 1758 | updates = true; 1759 | } else { 1760 | knownTypes.set(mappedHash, type); 1761 | } 1762 | } 1763 | } 1764 | 1765 | /// Determine names for types /// 1766 | 1767 | const usedNames = new Set([AnonymousType]); 1768 | 1769 | /** @type {[ts.Type, {symbolName: SymbolName}][]} */ 1770 | const needName = []; 1771 | for (const [type, parsed] of parsedCollectedTypes) { 1772 | switch (parsed.type) { 1773 | case "interface": { 1774 | if ( 1775 | parsed.typeParameters || 1776 | parsed.baseTypes.length > 0 || 1777 | (parsed.symbolName[0] !== AnonymousType && 1778 | !isSimpleFunction(parsed) && 1779 | exposedType !== type) 1780 | ) { 1781 | needName.push([type, parsed]); 1782 | } 1783 | break; 1784 | } 1785 | case "union": 1786 | case "intersection": { 1787 | if ( 1788 | parsed.typeParameters || 1789 | (parsed.symbolName[0] !== AnonymousType && exposedType !== type) 1790 | ) { 1791 | needName.push([type, parsed]); 1792 | } 1793 | break; 1794 | } 1795 | case "class": 1796 | case "namespace": 1797 | case "symbol": 1798 | case "import": { 1799 | needName.push([type, parsed]); 1800 | break; 1801 | } 1802 | case "namespace": { 1803 | for (const [name, exp] of parsed.exports) { 1804 | const parsedExport = parsedCollectedTypes.get(exp.type); 1805 | if ( 1806 | parsedExport.type === "typeof class" || 1807 | parsedExport.type === "namespace" || 1808 | parsedExport.type === "symbol" 1809 | ) { 1810 | continue; 1811 | } 1812 | usedNames.add(name); 1813 | } 1814 | needName.push([type, parsed]); 1815 | break; 1816 | } 1817 | } 1818 | } 1819 | 1820 | const { nameMapping } = options; 1821 | 1822 | const nameToQueueEntry = new Map(); 1823 | const findName = (symbolName, requeueOnConflict = false) => { 1824 | const key = symbolName.join(" "); 1825 | for (const wishedName of Object.keys(nameMapping)) { 1826 | if (nameMapping[wishedName].test(key)) { 1827 | symbolName = [wishedName, ...symbolName]; 1828 | } 1829 | } 1830 | let name; 1831 | for (let i = 1; i <= symbolName.length; i++) { 1832 | name = joinIdentifer(symbolName.slice(0, i)); 1833 | if (!usedNames.has(name)) { 1834 | usedNames.add(name); 1835 | return name; 1836 | } 1837 | if (requeueOnConflict) { 1838 | if (verbose) { 1839 | console.log( 1840 | `Naming conflict: ${name} can't be used for ${symbolName.join( 1841 | " ", 1842 | )}`, 1843 | ); 1844 | } 1845 | const item = nameToQueueEntry.get(name); 1846 | if (item) { 1847 | needName.push(item); 1848 | nameToQueueEntry.set(name, undefined); 1849 | } 1850 | } 1851 | } 1852 | let i = 1; 1853 | while (usedNames.has(`${name}_${i}`)) i++; 1854 | usedNames.add(`${name}_${i}`); 1855 | return `${name}_${i}`; 1856 | }; 1857 | 1858 | const typeToVariable = new Map(); 1859 | for (const entry of needName) { 1860 | const [type, item] = entry; 1861 | let { symbolName } = item; 1862 | const name = findName(symbolName, true); 1863 | typeToVariable.set(type, name); 1864 | nameToQueueEntry.set(name, entry); 1865 | } 1866 | 1867 | /// Determine code for types 1868 | 1869 | /** @type {Set} */ 1870 | const declarations = new Set(); 1871 | /** @type {Map} */ 1872 | const declarationKeys = new Map(); 1873 | /** @type {Map>} */ 1874 | const imports = new Map(); 1875 | /** @type {Set} */ 1876 | const importDeclarations = new Set(); 1877 | /** @type {Map} */ 1878 | const emitDeclarations = new Map(); 1879 | /** @type {string[]} */ 1880 | const exports = []; 1881 | const typeToCode = new TupleMap(); 1882 | 1883 | /** 1884 | * @param {string} key key for sorting 1885 | * @param {string} text content 1886 | * @returns {void} 1887 | */ 1888 | const addDeclaration = (key, text) => { 1889 | declarations.add(text); 1890 | declarationKeys.set(text, key); 1891 | }; 1892 | 1893 | const queueDeclaration = (type, variable, fn) => { 1894 | if (!variable) { 1895 | throw new Error( 1896 | `variable missing for queueDeclaration of ${checker.typeToString( 1897 | type, 1898 | )}`, 1899 | ); 1900 | } 1901 | emitDeclarations.set(type, () => { 1902 | const oldCodeGenerationContext = codeGenerationContext; 1903 | codeGenerationContext = variable; 1904 | const text = fn(); 1905 | codeGenerationContext = oldCodeGenerationContext; 1906 | declarations.add(text); 1907 | declarationKeys.set(text, variable); 1908 | }); 1909 | }; 1910 | 1911 | /** 1912 | * @param {string} exportName exported name 1913 | * @param {string} name local identifier name 1914 | * @param {string} from source file 1915 | * @returns {void} 1916 | */ 1917 | const addImport = (exportName, name, from) => { 1918 | if (!exportName.includes(".")) { 1919 | let set = imports.get(from); 1920 | if (set === undefined) { 1921 | imports.set(from, (set = new Set())); 1922 | } 1923 | set.add(exportName === name ? name : `${exportName} as ${name}`); 1924 | } else { 1925 | importDeclarations.add( 1926 | `type ${name} = import(${JSON.stringify(from)}).${exportName};`, 1927 | ); 1928 | } 1929 | }; 1930 | 1931 | const extractOptional = (code, optional = false) => { 1932 | if (code.startsWith("(undefined | ")) { 1933 | return { 1934 | code: "(" + code.slice("(undefined | ".length), 1935 | optional: true, 1936 | }; 1937 | } else if (code.endsWith(" | undefined)")) { 1938 | return { 1939 | code: code.slice(0, -" | undefined)".length) + ")", 1940 | optional: true, 1941 | }; 1942 | } 1943 | return { 1944 | code, 1945 | optional, 1946 | }; 1947 | }; 1948 | 1949 | /** 1950 | * @param {ParsedSignature} sig the signature 1951 | * @param {Set} typeArgs type args specified in context 1952 | * @param {"arrow" | "constructor" | "class-constructor" | "method" | undefined} type type of generated code 1953 | * @returns {string} code 1954 | */ 1955 | const sigToString = (sig, typeArgs, type = undefined) => { 1956 | const sigTypeArgs = sig.typeParameters 1957 | ? `<${sig.typeParameters 1958 | .map((t) => getCode(t, typeArgs, "in type args")) 1959 | .join(", ")}>` 1960 | : ""; 1961 | const innerTypeArgs = new Set(typeArgs); 1962 | if (sig.typeParameters) { 1963 | for (const t of sig.typeParameters) innerTypeArgs.add(t); 1964 | } 1965 | let canBeOptional = true; 1966 | const args = `(${ 1967 | sig.thisType 1968 | ? `this: ${getCode(sig.thisType, innerTypeArgs)}` + 1969 | (sig.args.length > 0 ? ", " : "") 1970 | : "" 1971 | } ${sig.args 1972 | .slice() 1973 | .reverse() 1974 | .map((arg) => { 1975 | const { code, optional } = canBeOptional 1976 | ? extractOptional(getCode(arg.type, innerTypeArgs), arg.optional) 1977 | : { 1978 | code: getCode(arg.type, innerTypeArgs), 1979 | optional: false, 1980 | }; 1981 | const isCurrentOptional = optional && !arg.spread; 1982 | canBeOptional = canBeOptional && isCurrentOptional; 1983 | return `${arg.spread ? "..." : ""}${arg.name}${ 1984 | isCurrentOptional ? "?" : "" 1985 | }: ${code}`; 1986 | }) 1987 | .reverse() 1988 | .join(", ")})`; 1989 | switch (type) { 1990 | case "arrow": 1991 | return `${sigTypeArgs}${args} => ${getCode( 1992 | sig.returnType, 1993 | innerTypeArgs, 1994 | )}`; 1995 | case "class-constructor": 1996 | return `constructor${args}`; 1997 | case "constructor": 1998 | return `new ${sigTypeArgs}${args}: ${getCode( 1999 | sig.returnType, 2000 | innerTypeArgs, 2001 | )}`; 2002 | case "method": 2003 | return `${sigTypeArgs}${args}: ${getCode( 2004 | sig.returnType, 2005 | innerTypeArgs, 2006 | )}`; 2007 | default: 2008 | return `${sigTypeArgs}${args}: ${getCode( 2009 | sig.returnType, 2010 | innerTypeArgs, 2011 | )}`; 2012 | } 2013 | }; 2014 | 2015 | /** 2016 | * @param {ts.Type} type the type 2017 | * @param {ParsedInterfaceType | MergedClassType} parsed parsed type 2018 | * @param {Set} typeArgs type args specified in context 2019 | * @returns {string[]} code items for interface 2020 | */ 2021 | const getInterfaceItems = (type, parsed, typeArgs) => { 2022 | const items = []; 2023 | for (const construct of parsed.constructors) { 2024 | items.push( 2025 | `${construct.documentation}${sigToString( 2026 | construct, 2027 | typeArgs, 2028 | parsed.type === "interface" ? "constructor" : "class-constructor", 2029 | )}`, 2030 | ); 2031 | } 2032 | if (parsed.type === "interface") { 2033 | for (const call of parsed.calls) { 2034 | items.push(`${call.documentation}${sigToString(call, typeArgs)}`); 2035 | } 2036 | } 2037 | if (parsed.numberIndex) { 2038 | items.push(`[index: number]: ${getCode(parsed.numberIndex, typeArgs)}`); 2039 | } 2040 | if (parsed.stringIndex) { 2041 | items.push(`[index: string]: ${getCode(parsed.stringIndex, typeArgs)}`); 2042 | } 2043 | 2044 | const hasIteratorInBaseType = (type) => { 2045 | const parsed = parsedCollectedTypes.get(type); 2046 | if (!parsed || parsed.type !== "reference") return false; 2047 | const parsedTarget = parsedCollectedTypes.get(parsed.target); 2048 | return ( 2049 | parsedTarget.type === "primitive" && 2050 | [ 2051 | "Array", 2052 | "Map", 2053 | "Set", 2054 | "String", 2055 | "Int8Array", 2056 | "Uint8Array", 2057 | "Uint8ClampedArray", 2058 | "Int16Array", 2059 | "Uint16Array", 2060 | "Int32Array", 2061 | "Uint32Array", 2062 | "Float16Array", 2063 | "Float32Array", 2064 | "Float64Array", 2065 | ].includes(parsedTarget.name) 2066 | ); 2067 | }; 2068 | 2069 | const handleProperties = (properties, prefix = "") => { 2070 | for (const [ 2071 | name, 2072 | { getter, type: propType, optional, readonly, method, documentation }, 2073 | ] of properties) { 2074 | if (method) { 2075 | if ( 2076 | name === "[Symbol.iterator]" && 2077 | parsed.type === "class" && 2078 | parsed.baseType && 2079 | hasIteratorInBaseType(parsed.baseType) 2080 | ) { 2081 | continue; 2082 | } 2083 | let methodInfo = parsedCollectedTypes.get(propType); 2084 | while ( 2085 | methodInfo.type === "reference" && 2086 | methodInfo.typeArgumentsWithoutDefaults.length === 0 2087 | ) { 2088 | methodInfo = parsedCollectedTypes.get(methodInfo.target); 2089 | } 2090 | if ( 2091 | methodInfo.type === "interface" && 2092 | methodInfo.baseTypes.length === 0 && 2093 | methodInfo.constructors.length === 0 && 2094 | !methodInfo.numberIndex && 2095 | !methodInfo.stringIndex && 2096 | methodInfo.properties.size === 0 2097 | ) { 2098 | for (const call of methodInfo.calls) { 2099 | const docs = new Set([ 2100 | documentation, 2101 | methodInfo.documentation, 2102 | call.documentation, 2103 | ]); 2104 | items.push( 2105 | `${Array.from(docs).join("")}${prefix}${name}${sigToString( 2106 | call, 2107 | typeArgs, 2108 | "method", 2109 | )}`, 2110 | ); 2111 | } 2112 | continue; 2113 | } else if (verbose) { 2114 | console.log( 2115 | `Method ${name} has weird type ${getCode(propType, typeArgs)} (${ 2116 | methodInfo.type 2117 | })`, 2118 | ); 2119 | } 2120 | } 2121 | const { code, optional: opt } = extractOptional( 2122 | getCode(propType, typeArgs), 2123 | optional, 2124 | ); 2125 | if (!getter) { 2126 | const p = prefix + (readonly ? "readonly " : ""); 2127 | if (opt) { 2128 | items.push(`${documentation}${p}${name}?: ${code}`); 2129 | } else { 2130 | items.push(`${documentation}${p}${name}: ${code}`); 2131 | } 2132 | } else { 2133 | items.push(`${documentation}get ${name}(): ${code}`); 2134 | } 2135 | } 2136 | }; 2137 | 2138 | handleProperties(parsed.properties); 2139 | if (parsed.type === "class" && parsed.correspondingType) { 2140 | handleProperties(parsed.staticProperties, "static "); 2141 | } 2142 | return items; 2143 | }; 2144 | 2145 | /** 2146 | * @param {ts.Type} type the type 2147 | * @param {Set} typeArgs type args specified in context 2148 | * @param {string} state generation state 2149 | * @returns {string} code 2150 | */ 2151 | const getCodeInternal = (type, typeArgs, state) => { 2152 | const parsed = parsedCollectedTypes.get(type); 2153 | if (!parsed) return "unknown /* no parsed data */"; 2154 | switch (parsed.type) { 2155 | case "primitive": 2156 | return parsed.name; 2157 | case "typeParameter": { 2158 | let code = parsed.name; 2159 | if (state === "in type args") { 2160 | if (parsed.constraint) { 2161 | const constraint = getCode(parsed.constraint, typeArgs, state); 2162 | code += ` extends ${constraint}`; 2163 | } 2164 | if (parsed.defaultValue) { 2165 | const defaultValue = getCode(parsed.defaultValue, typeArgs, state); 2166 | code += ` = ${defaultValue}`; 2167 | } 2168 | } 2169 | return code; 2170 | } 2171 | case "template": { 2172 | let code = "`"; 2173 | code += parsed.texts[0]; 2174 | for (let i = 0; i < parsed.types.length; i++) { 2175 | code += `\${${getCode(parsed.types[i], typeArgs)}}`; 2176 | code += parsed.texts[i + 1]; 2177 | } 2178 | code += "`"; 2179 | return code; 2180 | } 2181 | case "index": { 2182 | return `(${getCode(parsed.objectType, typeArgs)})[${getCode( 2183 | parsed.indexType, 2184 | typeArgs, 2185 | )}]`; 2186 | } 2187 | case "union": 2188 | case "intersection": { 2189 | /** 2190 | * @param {Set} typeArgs type args specified in context 2191 | * @returns {string} code 2192 | */ 2193 | const code = (typeArgs) => 2194 | `(${Array.from(new Set(parsed.types.map((t) => getCode(t, typeArgs)))) 2195 | .join(parsed.type === "intersection" ? " & " : " | ") 2196 | .replace(/(^|\| )false \| true($| \|)/g, "$1boolean$2")})`; 2197 | 2198 | const variable = typeToVariable.get(type); 2199 | if (variable) { 2200 | queueDeclaration( 2201 | type, 2202 | variable, 2203 | () => 2204 | `type ${variable}${ 2205 | parsed.typeParameters 2206 | ? `<${parsed.typeParameters 2207 | .map((t) => getCode(t, new Set(), "in type args")) 2208 | .join(", ")}>` 2209 | : "" 2210 | } = ${code(new Set())};`, 2211 | ); 2212 | if (state !== "with type args" && parsed.typeParameters) { 2213 | return `${variable}<${parsed.typeParameters.map((t) => 2214 | getCode(t, typeArgs), 2215 | )}>`; 2216 | } 2217 | return `${variable}`; 2218 | } 2219 | return code(typeArgs); 2220 | } 2221 | case "reference": { 2222 | if (parsed.typeArguments.length === 0) 2223 | return getCode(parsed.target, typeArgs, state); 2224 | if (parsed.typeArgumentsWithoutDefaults.length === 0) 2225 | return getCode(parsed.target, typeArgs, "with type args"); 2226 | const parsedTarget = parsedCollectedTypes.get(parsed.target); 2227 | if (parsedTarget && parsedTarget.type === "primitive") { 2228 | if (parsedTarget.name === "[]") { 2229 | return `[${parsed.typeArgumentsWithoutDefaults 2230 | .map((t) => getCode(t, typeArgs)) 2231 | .join(", ")}]`; 2232 | } else if (parsedTarget.name === "[...]") { 2233 | const items = parsed.typeArgumentsWithoutDefaults.map((t) => 2234 | getCode(t, typeArgs), 2235 | ); 2236 | const last = items.pop(); 2237 | return `[${items.join(", ")}, ...(${last})[]]`; 2238 | } else if ( 2239 | parsedTarget.name === "Array" && 2240 | parsed.typeArgumentsWithoutDefaults.length === 1 2241 | ) { 2242 | return `(${getCode( 2243 | parsed.typeArgumentsWithoutDefaults[0], 2244 | typeArgs, 2245 | )})[]`; 2246 | } 2247 | } 2248 | 2249 | const symbol = type.getSymbol(); 2250 | const typeArgumentsWithoutDefaults = 2251 | symbol && isArrayBufferLike(getFullEscapedName(symbol)) 2252 | ? "" 2253 | : `<${parsed.typeArgumentsWithoutDefaults 2254 | .map((t) => getCode(t, typeArgs)) 2255 | .join(", ")}>`; 2256 | 2257 | return `${getCode( 2258 | parsed.target, 2259 | typeArgs, 2260 | "with type args", 2261 | )}${typeArgumentsWithoutDefaults}`; 2262 | } 2263 | case "interface": { 2264 | const variable = typeToVariable.get(type); 2265 | if (variable !== undefined) { 2266 | queueDeclaration(type, variable, () => { 2267 | const typeArgs = new Set(parsed.typeParameters); 2268 | return `${parsed.documentation}declare interface ${variable}${ 2269 | parsed.typeParameters 2270 | ? `<${parsed.typeParameters 2271 | .map((t) => getCode(t, new Set(), "in type args")) 2272 | .join(", ")}>` 2273 | : "" 2274 | }${ 2275 | parsed.baseTypes.length > 0 2276 | ? ` extends ${parsed.baseTypes 2277 | .map((t) => getCode(t, typeArgs)) 2278 | .join(", ")}` 2279 | : "" 2280 | } {\n${getInterfaceItems(type, parsed, typeArgs) 2281 | .map((i) => `\t${i};`) 2282 | .join("\n")}\n}`; 2283 | }); 2284 | if (state !== "with type args" && parsed.typeParameters) { 2285 | return `${variable}<${parsed.typeParameters.map((t) => 2286 | getCode(t, typeArgs), 2287 | )}>`; 2288 | } 2289 | return `${variable}`; 2290 | } 2291 | if (isSimpleFunction(parsed)) { 2292 | return `(${sigToString(parsed.calls[0], typeArgs, "arrow")})`; 2293 | } 2294 | return `{ ${getInterfaceItems(type, parsed, typeArgs).join("; ")} }`; 2295 | } 2296 | case "typeof class": 2297 | case "class": { 2298 | const classType = 2299 | parsed.type === "typeof class" ? parsed.correspondingType : type; 2300 | const variable = typeToVariable.get(classType); 2301 | queueDeclaration(classType, variable, () => { 2302 | const parsed = /** @type {MergedClassType} */ ( 2303 | parsedCollectedTypes.get(classType) 2304 | ); 2305 | const typeArgs = new Set(parsed.typeParameters); 2306 | return `declare ${ 2307 | parsed.constructors.length === 0 ? "abstract class" : "class" 2308 | } ${variable}${ 2309 | parsed.typeParameters 2310 | ? `<${parsed.typeParameters 2311 | .map((t) => getCode(t, new Set(), "in type args")) 2312 | .join(", ")}>` 2313 | : "" 2314 | }${ 2315 | parsed.baseType 2316 | ? ` extends ${getCode(parsed.baseType, typeArgs)}` 2317 | : "" 2318 | } {\n${getInterfaceItems(classType, parsed, typeArgs) 2319 | .map((i) => `\t${i};`) 2320 | .join("\n")}\n}`; 2321 | }); 2322 | if (parsed.type === "typeof class") { 2323 | return `typeof ${variable}`; 2324 | } 2325 | if (state !== "with type args" && parsed.typeParameters) { 2326 | return `${variable}<${parsed.typeParameters.map((t) => 2327 | getCode(t, typeArgs), 2328 | )}>`; 2329 | } 2330 | return `${variable}`; 2331 | } 2332 | case "namespace": { 2333 | const ns = (variable, exportNamespace) => { 2334 | const exports = []; 2335 | const declarations = []; 2336 | const exposedNames = new Set(); 2337 | for (const [ 2338 | name, 2339 | { type: exportedType, optional, readonly, getter, method }, 2340 | ] of parsed.exports) { 2341 | const code = getCode( 2342 | /** @type {ts.Type} */ 2343 | (exportedType), 2344 | new Set(), 2345 | `in namespace ${name}`, 2346 | ); 2347 | if (code.startsWith("export ")) { 2348 | declarations.push(code); 2349 | } else if (/^typeof [A-Za-z_0-9]+$/.test(code)) { 2350 | const exportName = code.slice(`typeof `.length); 2351 | exports.push( 2352 | exportName === name ? name : `${exportName} as ${name}`, 2353 | ); 2354 | } else if (name === "default") { 2355 | declarations.push( 2356 | `${readonly || getter ? "const" : "let"} _default: ${code};\n`, 2357 | ); 2358 | exports.push(`_default as default`); 2359 | } else { 2360 | declarations.push( 2361 | `export ${ 2362 | readonly || getter ? "const" : "let" 2363 | } ${name}: ${code};\n`, 2364 | ); 2365 | } 2366 | } 2367 | if (type === exposedType) { 2368 | for (const [name, type] of typeExports) { 2369 | if (exposedNames.has(name)) continue; 2370 | const code = getCode(type, new Set()); 2371 | if (/^[A-Za-z_0-9]+(<.+>)?$/.test(code)) { 2372 | const codeWithoutTemplateArgs = code.replace(/<.+>/, ""); 2373 | exports.push( 2374 | codeWithoutTemplateArgs === name 2375 | ? name 2376 | : `${codeWithoutTemplateArgs} as ${name}`, 2377 | ); 2378 | } else { 2379 | declarations.push(`export type ${name} = ${code};\n`); 2380 | } 2381 | } 2382 | } 2383 | return `${parsed.calls 2384 | .map( 2385 | (call) => 2386 | `${ 2387 | exportNamespace ? "export" : "declare" 2388 | } function ${variable}${sigToString( 2389 | call, 2390 | new Set(), 2391 | "method", 2392 | )};\n`, 2393 | ) 2394 | .join("")}${ 2395 | exportNamespace ? "export" : "declare" 2396 | } namespace ${variable} {\n${declarations.join("")}${ 2397 | exports.length > 0 ? `export { ${exports.join(", ")} }` : "" 2398 | }\n}`; 2399 | }; 2400 | if (state.startsWith("in namespace ")) { 2401 | return ns(state.slice("in namespace ".length), true); 2402 | } 2403 | const variable = typeToVariable.get(type); 2404 | queueDeclaration(type, variable, () => ns(variable)); 2405 | return `typeof ${variable}`; 2406 | } 2407 | case "symbol": { 2408 | const variable = typeToVariable.get(type); 2409 | queueDeclaration( 2410 | type, 2411 | variable, 2412 | () => `declare const ${variable}: unique symbol;`, 2413 | ); 2414 | return `typeof ${variable}`; 2415 | } 2416 | case "import": { 2417 | const variable = typeToVariable.get(type); 2418 | addImport(parsed.exportName, variable, parsed.from); 2419 | return ( 2420 | (parsed.isValue && !type.isClass() && state !== "with type args" 2421 | ? "typeof " 2422 | : "") + variable 2423 | ); 2424 | } 2425 | } 2426 | return `unknown /* failed to generate code: ${parsed.type} */`; 2427 | }; 2428 | 2429 | let codeGenerationContext = ""; 2430 | 2431 | const unusedTempNames = new TupleMap(); 2432 | 2433 | /** 2434 | * @param {ts.Type} type the type 2435 | * @param {Set} typeArgs type args specified in context 2436 | * @param {string} state generation state 2437 | * @returns {string} code 2438 | */ 2439 | const getCode = (type, typeArgs, state = "") => { 2440 | const tuple = [type, ...typeArgs, state]; 2441 | const code = typeToCode.get(tuple); 2442 | if (code !== undefined) { 2443 | unusedTempNames.delete(tuple); 2444 | return code; 2445 | } 2446 | const parsed = parsedCollectedTypes.get(type); 2447 | const tempName = findName( 2448 | (parsed && "symbolName" in parsed && parsed.symbolName) || [ 2449 | codeGenerationContext + "AnonymousCircularType", 2450 | ], 2451 | ); 2452 | typeToCode.set(tuple, tempName); 2453 | unusedTempNames.add(tuple); 2454 | const newCode = getCodeInternal(type, typeArgs, state); 2455 | const used = !unusedTempNames.has(tuple); 2456 | unusedTempNames.delete(tuple); 2457 | if (used) { 2458 | addDeclaration(tempName, `type ${tempName} = ${newCode};`); 2459 | } else { 2460 | usedNames.delete(tempName); 2461 | typeToCode.set(tuple, newCode); 2462 | } 2463 | return newCode; 2464 | }; 2465 | 2466 | if (exposedType) { 2467 | const code = getCode(exposedType, new Set()); 2468 | if (code.startsWith("typeof ")) { 2469 | exports.push(`export = ${code.slice("typeof ".length)};`); 2470 | } else { 2471 | const exportsName = findName(["exports"]); 2472 | exports.push(`declare const ${exportsName}: ${code};`); 2473 | exports.push(`export = ${exportsName};`); 2474 | } 2475 | } 2476 | 2477 | for (const [, fn] of emitDeclarations) { 2478 | fn(); 2479 | } 2480 | 2481 | const outputFilePath = path.resolve(root, outputFile); 2482 | 2483 | const sortedDeclarations = [...declarations].sort((a, b) => { 2484 | const ak = /** @type {string} */ (declarationKeys.get(a)); 2485 | const bk = /** @type {string} */ (declarationKeys.get(b)); 2486 | if (ak < bk) return -1; 2487 | if (ak > bk) return 1; 2488 | return 0; 2489 | }); 2490 | 2491 | let source = [ 2492 | "/*", 2493 | " * This file was automatically generated.", 2494 | " * DO NOT MODIFY BY HAND.", 2495 | " * Run `yarn fix:special` to update", 2496 | " */", 2497 | "", 2498 | ...[...imports.keys()] 2499 | .sort() 2500 | .map( 2501 | (from) => 2502 | `import { ${[...imports.get(from)] 2503 | .sort() 2504 | .join(", ")} } from ${JSON.stringify(from)}`, 2505 | ), 2506 | ...[...importDeclarations].sort(), 2507 | "", 2508 | ...sortedDeclarations, 2509 | "", 2510 | ...exports, 2511 | ].join("\n"); 2512 | try { 2513 | const prettierOptions = await prettier.resolveConfig(outputFilePath); 2514 | if (!prettierOptions) { 2515 | console.error("Prettier options not found"); 2516 | return; 2517 | } 2518 | 2519 | source = await prettier.format(source, prettierOptions); 2520 | } catch (e) { 2521 | exitCode = 1; 2522 | console.error(e.message); 2523 | } 2524 | 2525 | let needUpdate = true; 2526 | try { 2527 | if ((await fs.readFile(outputFilePath, "utf-8")) === source) { 2528 | needUpdate = false; 2529 | } 2530 | } catch (e) { 2531 | // ignore 2532 | } 2533 | 2534 | if (needUpdate) { 2535 | if (doWrite) { 2536 | await fs.writeFile(outputFilePath, source); 2537 | console.error("types.d.ts updated."); 2538 | } else { 2539 | exitCode = 1; 2540 | console.error("types.d.ts need to be updated."); 2541 | console.error("run 'yarn fix:special' to update."); 2542 | } 2543 | } 2544 | 2545 | // const hint = typeNameHints.get(type); 2546 | // if (!hint) continue; 2547 | // const name = `${hint.source.fileName.replace(/[^A-Za-z]+/g, "_")}_${ 2548 | // hint.symbol ? hint.symbol.getName().replace(/[^A-Za-z]+/g, "_") : "" 2549 | // }_${hint.name || uniqueId++}`; 2550 | 2551 | process.exitCode = exitCode; 2552 | })(); 2553 | --------------------------------------------------------------------------------