UNPKG

@jsdocs-io/extractor

Version:

Analyze and extract the API from npm packages

2,099 lines (1,907 loc) 60 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var perf_hooks = require('perf_hooks'); var queryRegistry = require('query-registry'); var debug = require('debug'); var tsm = require('ts-morph'); var prettier = require('prettier'); var tsdoc = require('@microsoft/tsdoc'); var HostedGitInfo = require('hosted-git-info'); var path = require('path'); var got = require('got'); var gunzipMaybe = require('gunzip-maybe'); var stream = require('stream'); var util = require('util'); var concat = require('concat-stream'); var tar = require('tar-stream'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n["default"] = e; return n; } var debug__default = /*#__PURE__*/_interopDefaultLegacy(debug); var tsm__namespace = /*#__PURE__*/_interopNamespace(tsm); var prettier__namespace = /*#__PURE__*/_interopNamespace(prettier); var tsdoc__namespace = /*#__PURE__*/_interopNamespace(tsdoc); var HostedGitInfo__default = /*#__PURE__*/_interopDefaultLegacy(HostedGitInfo); var path__namespace = /*#__PURE__*/_interopNamespace(path); var got__default = /*#__PURE__*/_interopDefaultLegacy(got); var gunzipMaybe__default = /*#__PURE__*/_interopDefaultLegacy(gunzipMaybe); var stream__default = /*#__PURE__*/_interopDefaultLegacy(stream); var concat__default = /*#__PURE__*/_interopDefaultLegacy(concat); var tar__default = /*#__PURE__*/_interopDefaultLegacy(tar); const log = /*#__PURE__*/debug__default["default"]('@jsdocs-io/extractor'); function getOverview({ indexFile }) { var _indexFile$getDescend, _indexFile$getDescend2; return (_indexFile$getDescend = indexFile.getDescendantsOfKind(tsm__namespace.SyntaxKind.JSDocTag).find(tag => tag.getTagName() === 'packageDocumentation')) == null ? void 0 : (_indexFile$getDescend2 = _indexFile$getDescend.getParentIfKind(tsm__namespace.SyntaxKind.JSDocComment)) == null ? void 0 : _indexFile$getDescend2.getText(); } /** * `DeclarationKinds` lists the possible kinds of declarations */ exports.DeclarationKinds = void 0; (function (DeclarationKinds) { DeclarationKinds["VariableDeclaration"] = "VariableDeclaration"; DeclarationKinds["FunctionDeclaration"] = "FunctionDeclaration"; DeclarationKinds["ClassDeclaration"] = "ClassDeclaration"; DeclarationKinds["ClassConstructorDeclaration"] = "ClassConstructorDeclaration"; DeclarationKinds["ClassPropertyDeclaration"] = "ClassPropertyDeclaration"; DeclarationKinds["ClassMethodDeclaration"] = "ClassMethodDeclaration"; DeclarationKinds["InterfaceDeclaration"] = "InterfaceDeclaration"; DeclarationKinds["InterfacePropertyDeclaration"] = "InterfacePropertyDeclaration"; DeclarationKinds["InterfaceMethodDeclaration"] = "InterfaceMethodDeclaration"; DeclarationKinds["InterfaceConstructSignatureDeclaration"] = "InterfaceConstructSignatureDeclaration"; DeclarationKinds["InterfaceCallSignatureDeclaration"] = "InterfaceCallSignatureDeclaration"; DeclarationKinds["InterfaceIndexSignatureDeclaration"] = "InterfaceIndexSignatureDeclaration"; DeclarationKinds["EnumDeclaration"] = "EnumDeclaration"; DeclarationKinds["EnumMemberDeclaration"] = "EnumMemberDeclaration"; DeclarationKinds["TypeAliasDeclaration"] = "TypeAliasDeclaration"; DeclarationKinds["NamespaceDeclaration"] = "NamespaceDeclaration"; })(exports.DeclarationKinds || (exports.DeclarationKinds = {})); function isVariableDeclaration(declaration) { return declaration.kind === exports.DeclarationKinds.VariableDeclaration; } function isFunctionDeclaration(declaration) { return declaration.kind === exports.DeclarationKinds.FunctionDeclaration; } function isClassDeclaration(declaration) { return declaration.kind === exports.DeclarationKinds.ClassDeclaration; } function isClassConstructorDeclaration(declaration) { return declaration.kind === exports.DeclarationKinds.ClassConstructorDeclaration; } function isClassPropertyDeclaration(declaration) { return declaration.kind === exports.DeclarationKinds.ClassPropertyDeclaration; } function isClassMethodDeclaration(declaration) { return declaration.kind === exports.DeclarationKinds.ClassMethodDeclaration; } function isInterfaceDeclaration(declaration) { return declaration.kind === exports.DeclarationKinds.InterfaceDeclaration; } function isInterfacePropertyDeclaration(declaration) { return declaration.kind === exports.DeclarationKinds.InterfacePropertyDeclaration; } function isInterfaceMethodDeclaration(declaration) { return declaration.kind === exports.DeclarationKinds.InterfaceMethodDeclaration; } function isInterfaceConstructSignatureDeclaration(declaration) { return declaration.kind === exports.DeclarationKinds.InterfaceConstructSignatureDeclaration; } function isInterfaceCallSignatureDeclaration(declaration) { return declaration.kind === exports.DeclarationKinds.InterfaceCallSignatureDeclaration; } function isInterfaceIndexSignatureDeclaration(declaration) { return declaration.kind === exports.DeclarationKinds.InterfaceIndexSignatureDeclaration; } function isEnumDeclaration(declaration) { return declaration.kind === exports.DeclarationKinds.EnumDeclaration; } function isEnumMemberDeclaration(declaration) { return declaration.kind === exports.DeclarationKinds.EnumMemberDeclaration; } function isTypeAliasDeclaration(declaration) { return declaration.kind === exports.DeclarationKinds.TypeAliasDeclaration; } function isNamespaceDeclaration(declaration) { return declaration.kind === exports.DeclarationKinds.NamespaceDeclaration; } const formatOptions = { semi: true, singleQuote: true, trailingComma: 'es5', tabWidth: 4, printWidth: 85, endOfLine: 'lf', arrowParens: 'always', parser: 'typescript' }; function formatFunctionSignature(text) { const varLike = `let ${text}`; return formatVariableSignature(varLike).replace(/^let\s/, ''); } function formatVariableSignature(text) { // Temporarily replace the invalid variable name `default` const escapedText = text.replace('default:', '_______:'); return formatText(escapedText).replace('_______:', 'default:'); } function formatClassMember(text) { const wrapped = `class C { ${text} }`; return formatWrappedMember(wrapped); } function formatInterfaceMember(text) { const wrapped = `interface I { ${text} }`; return formatWrappedMember(wrapped); } function formatEnumMember(text) { const wrapped = `enum E { ${text} }`; return formatWrappedMember(wrapped).replace(/,$/, ''); } function formatWrappedMember(text) { const formatted = formatText(text); const lines = formatted.split('\n'); // Remove wrapper and member indentation const member = lines.slice(1, lines.length - 1).map(line => line.replace(/^\s{4}/, '')).join('\n'); return member; } function formatText(text) { let formatted = text.trim().replace(/^export\s+/, '').replace(/^default\s+/, '').replace(/^declare\s+/, ''); try { formatted = prettier__namespace.format(formatted, formatOptions); } catch (err) { // istanbul ignore next log('formatText: formatting error: %O', { err }); } return formatted.trim(); } function getApparentType({ declaration }) { // See https://github.com/dsherret/ts-morph/issues/453#issuecomment-427405736 // and https://github.com/dsherret/ts-morph/issues/453#issuecomment-667578386 return declaration.getType().getApparentType().getText(declaration, tsm__namespace.ts.TypeFormatFlags.NoTruncation | tsm__namespace.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope).replace(/^Number$/, 'number').replace(/^Boolean$/, 'boolean').replace(/^String$/, 'string'); } function getJSDocs({ declaration }) { const declarations = getDeclarationsWithDocs({ declaration }); const allDocs = declarations.flatMap(declaration => { const doc = getLastJSDoc({ declaration }); return doc ? doc : []; }); return Array.from(new Set(allDocs)); } function getDeclarationsWithDocs({ declaration }) { if (tsm__namespace.Node.isVariableDeclaration(declaration)) { return [declaration.getVariableStatementOrThrow()]; } if (tsm__namespace.Node.isExpression(declaration)) { return [declaration.getParent()]; } // Make functions and class methods share the same docs, // that is one declaration with multiple (overload) docs, // since they have their signature built from the typechecker. // Exclude constructors since their signatures are built manually and // thus each constructor needs its own doc. if (tsm__namespace.Node.isOverloadable(declaration) && !tsm__namespace.Node.isConstructorDeclaration(declaration)) { const overloads = declaration.getOverloads(); const implementation = declaration.getImplementation(); return [...overloads, ...(implementation ? [implementation] : [])]; } // Make interface methods share the same docs as for overloadable nodes if (tsm__namespace.Node.isMethodSignature(declaration) && declaration.getParent().getKind() === tsm__namespace.SyntaxKind.InterfaceDeclaration) { const methodName = declaration.getName(); const overloads = declaration.getParentIfKindOrThrow(tsm__namespace.SyntaxKind.InterfaceDeclaration).getMethods().filter(method => method.getName() === methodName); return overloads; } return [declaration]; } function getLastJSDoc({ declaration }) { var _declaration$getLastC; // Get the doc closest to the declaration signature const doc = (_declaration$getLastC = declaration.getLastChildByKind(tsm__namespace.SyntaxKind.JSDocComment)) == null ? void 0 : _declaration$getLastC.getText(); if (!doc) { return undefined; } // The first declaration after package documentation // should not inherit that jsdoc if it has none. // See `export-named-declaration-without-jsdoc.test.ts`. const isPackageDocumentation = new tsdoc__namespace.TSDocParser().parseString(doc).docComment.modifierTagSet.isPackageDocumentation(); if (isPackageDocumentation) { return undefined; } return doc; } function getModifiersText({ declaration }) { return declaration.getModifiers().flatMap(modifier => { // Ignore `public` modifier if (modifier.getKind() === tsm__namespace.SyntaxKind.PublicKeyword) { return []; } return modifier.getText(); }).join(' '); } function getWrapperSignature({ declaration }) { const parts = []; for (const child of declaration.getChildren()) { // Ignore documentation comments if (isJSDocComment(child)) { continue; } // Stop at body start (e.g., the first opening bracket of a class). if (isOpenBraceToken(child)) { break; } parts.push(child.getText()); } parts.push('{}'); const signature = parts.join(' '); return formatText(signature); } function isJSDocComment(node) { return node.getKind() === tsm__namespace.SyntaxKind.JSDocComment; } function isOpenBraceToken(node) { return node.getKind() === tsm__namespace.SyntaxKind.OpenBraceToken; } function isInternalDeclaration({ declaration, name = '' }) { return name.startsWith('_') || name.startsWith('#') || hasPrivateModifier({ declaration }) || hasInternalTagDoc({ declaration }); } function hasPrivateModifier({ declaration }) { return tsm__namespace.Node.isModifierable(declaration) && declaration.hasModifier(tsm__namespace.SyntaxKind.PrivateKeyword); } function hasInternalTagDoc({ declaration }) { const firstDoc = getJSDocs({ declaration })[0]; if (!firstDoc) { return false; } const parser = new tsdoc__namespace.TSDocParser(); return parser.parseString(firstDoc).docComment.modifierTagSet.isInternal(); } function sortByID(a, b) { return a.id.localeCompare(b.id); } function toID(...elements) { return elements.filter(Boolean).join('.'); } function isClass(declaration) { return tsm__namespace.Node.isClassDeclaration(declaration); } function newClass({ id, name, declaration, getSource, getType }) { const kind = exports.DeclarationKinds.ClassDeclaration; const docs = getJSDocs({ declaration }); const source = getSource({ declaration }); const isAbstract = declaration.isAbstract(); const signature = getWrapperSignature({ declaration }); const constructors = getClassConstructors({ classID: id, classDeclaration: declaration, getSource }); const members = getClassMembers({ classID: id, classDeclaration: declaration, getSource, getType }); return { kind, id, name, docs, source, signature, isAbstract, constructors, members }; } function getClassConstructors({ classID, classDeclaration, getSource }) { // `getConstructors()` returns all constructors for ambient modules // but only the implementation constructor in normal modules const declaration = classDeclaration.getConstructors()[0]; if (!declaration) { return []; } // Manually retrieve all constructors from the first constructor const overloads = declaration.getOverloads(); const implementation = declaration.getImplementation(); const constructors = [...overloads, ...(implementation ? [implementation] : [])]; return constructors.flatMap((declaration, index) => { if (isInternalDeclaration({ declaration })) { return []; } const kind = exports.DeclarationKinds.ClassConstructorDeclaration; const name = 'constructor'; const id = toID(classID, `${index}-${name}`); const docs = getJSDocs({ declaration }); const source = getSource({ declaration }); const signature = getClassConstructorSignature({ declaration }); return { kind, id, name, docs, source, signature }; }); } function getClassConstructorSignature({ declaration }) { const modifiers = declaration.getModifiers().map(modifier => modifier.getText()).join(' '); const params = declaration.getParameters().map(param => { const name = param.getName(); const type = getApparentType({ declaration: param }); const isRest = param.isRestParameter(); const dotsToken = isRest ? '...' : ''; const isOptional = param.isOptional(); const questionToken = !isRest && isOptional ? '?' : ''; return `${dotsToken}${name}${questionToken}: ${type}`; }).join(','); const signature = `${modifiers} constructor(${params});`; return formatClassMember(signature); } function getClassMembers({ classID, classDeclaration, getSource, getType }) { const seenMethods = new Set(); const members = [...classDeclaration.getStaticMembers(), ...classDeclaration.getInstanceMembers()].flatMap(declaration => { const name = declaration.getName(); const id = toID(classID, name); if (isInternalDeclaration({ declaration, name })) { return []; } if (tsm__namespace.Node.isPropertyDeclaration(declaration) || tsm__namespace.Node.isParameterDeclaration(declaration)) { return newProperty$1({ id, name, declaration, getSource }); } if (tsm__namespace.Node.isGetAccessorDeclaration(declaration)) { return newGetAccessor({ id, name, declaration, getSource }); } if (tsm__namespace.Node.isMethodDeclaration(declaration)) { // Skip overloaded methods if (seenMethods.has(id)) { return []; } seenMethods.add(id); return newMethod$1({ id, name, declaration, getSource, getType }); } return []; }).sort(sortByID); return { properties: members.filter(isClassPropertyDeclaration), methods: members.filter(isClassMethodDeclaration) }; } function newProperty$1({ id, name, declaration, getSource }) { const kind = exports.DeclarationKinds.ClassPropertyDeclaration; const docs = getJSDocs({ declaration }); const source = getSource({ declaration }); const modifiersText = getModifiersText({ declaration }); const isStatic = tsm__namespace.Node.isPropertyDeclaration(declaration) && declaration.isStatic(); const isOptional = declaration.hasQuestionToken(); const optionalText = isOptional ? '?' : ''; const type = getApparentType({ declaration }); const signature = formatClassMember(`${modifiersText} ${name} ${optionalText}: ${type}`); return { kind, id, name, docs, source, signature, isStatic, type }; } function newGetAccessor({ id, name, declaration, getSource }) { const kind = exports.DeclarationKinds.ClassPropertyDeclaration; const docs = getJSDocs({ declaration }); const source = getSource({ declaration }); const isStatic = declaration.isStatic(); const isReadonly = declaration.getSetAccessor() === undefined; const type = getApparentType({ declaration: declaration }); const staticText = isStatic ? 'static' : ''; const readonlyText = isReadonly ? 'readonly' : ''; const signature = formatClassMember(`${staticText} ${readonlyText} ${name}: ${type}`); return { kind, id, name, docs, source, signature, isStatic, type }; } function newMethod$1({ id, name, declaration, getSource, getType }) { const kind = exports.DeclarationKinds.ClassMethodDeclaration; const docs = getJSDocs({ declaration }); const source = getSource({ declaration }); const isStatic = declaration.isStatic(); const modifiersText = getModifiersText({ declaration }); const type = getType({ declaration }); const signature = formatClassMember(`${modifiersText} ${name}: ${type}`); return { kind, id, name, docs, source, signature, isStatic, type }; } function isEnum(declaration) { return tsm__namespace.Node.isEnumDeclaration(declaration); } function newEnum({ id, name, declaration, getSource }) { const kind = exports.DeclarationKinds.EnumDeclaration; const docs = getJSDocs({ declaration }); const source = getSource({ declaration }); const isConst = declaration.isConstEnum(); // Keep members in original order for signature text and sort them later const members = getEnumMembers({ enumID: id, enumDeclaration: declaration, getSource }); const signature = getEnumSignature({ isConst, name, members }); members.sort(sortByID); return { kind, id, name, docs, source, signature, isConst, members }; } function getEnumMembers({ enumID, enumDeclaration, getSource }) { return enumDeclaration.getMembers().flatMap(declaration => { const name = declaration.getName(); if (isInternalDeclaration({ declaration, name })) { return []; } const kind = exports.DeclarationKinds.EnumMemberDeclaration; const id = toID(enumID, name); const docs = getJSDocs({ declaration }); const source = getSource({ declaration }); const signature = formatEnumMember(declaration.getText()); const value = declaration.getValue(); return { kind, id, name, docs, source, signature, value }; }); } function getEnumSignature({ isConst, name, members }) { const kind = isConst ? 'const enum' : 'enum'; const membersText = members.map(({ signature }) => signature).join(); const signature = `${kind} ${name} { ${membersText} }`; return formatText(signature); } function hasFunctionLikeType(node) { var _node$getTypeNode; const typeKind = (_node$getTypeNode = node.getTypeNode()) == null ? void 0 : _node$getTypeNode.getKind(); const hasFunctionType = typeKind === tsm__namespace.SyntaxKind.FunctionType; if (hasFunctionType) { return true; } const initializer = node.getInitializer(); if (!initializer) { return false; } const hasFunctionInitializer = tsm__namespace.Node.isArrowFunction(initializer) || tsm__namespace.Node.isFunctionExpression(initializer); return hasFunctionInitializer; } function hasVarLikeType(node) { return !hasFunctionLikeType(node); } function isVariable(declaration) { return tsm__namespace.Node.isVariableDeclaration(declaration) && hasVarLikeType(declaration); } function newVariable({ id, name, declaration, getSource, suggestedType }) { const kind = exports.DeclarationKinds.VariableDeclaration; const docs = getJSDocs({ declaration }); const source = getSource({ declaration }); const variableKind = getVariableKind({ declaration }); const type = getVariableType({ declaration, suggestedType }); const signature = getVariableSignature({ variableKind, name, type }); return { kind, id, name, docs, source, signature, variableKind, type }; } function getVariableKind({ declaration }) { return declaration.getVariableStatementOrThrow().getDeclarationKind().toString(); } function getVariableType({ declaration, suggestedType = 'any' }) { const apparentType = getApparentType({ declaration }); return apparentType !== 'any' ? apparentType : suggestedType; } function getVariableSignature({ variableKind, name, type }) { const signature = `${variableKind} ${name}: ${type}`; return formatVariableSignature(signature); } function isVariableAssignmentExpression(declaration) { return tsm__namespace.Node.isBinaryExpression(declaration) && tsm__namespace.Node.isIdentifier(declaration.getLeft()); } function newVariableAssignmentExpression({ id, name, declaration, getSource }) { const variableDeclaration = getVariableDeclaration({ declaration }); const suggestedType = getApparentType({ declaration: declaration.getRight() }); return newVariable({ id, name, declaration: variableDeclaration, getSource, suggestedType }); } function getVariableDeclaration({ declaration }) { return declaration.getLeft().getSymbol().getDeclarations()[0]; } function isExpression(declaration) { return tsm__namespace.Node.isExpression(declaration); } function newExpression({ id, name, declaration, getSource }) { const kind = exports.DeclarationKinds.VariableDeclaration; const docs = getJSDocs({ declaration }); const source = getSource({ declaration }); const variableKind = 'const'; const type = getApparentType({ declaration }); const signature = getVariableSignature({ variableKind, name, type }); return { kind, id, name, docs, source, signature, variableKind, type }; } function getFilename({ declaration }) { // Remove leading `/` from filepath return declaration.getSourceFile().getFilePath().replace(/^\//, ''); } function isFileModule(declaration) { return tsm__namespace.Node.isSourceFile(declaration); } function newFileModule({ id, name, declaration, declarations, getSource }) { const kind = exports.DeclarationKinds.NamespaceDeclaration; const docs = getFileModuleDocs({ declaration }); const source = getSource({ declaration }); const signature = getFileModuleSignature({ declaration }); return { kind, id, name, docs, source, signature, declarations }; } function getFileModuleDocs({ declaration }) { var _declaration$getDesce; const doc = (_declaration$getDesce = declaration.getDescendantsOfKind(tsm__namespace.SyntaxKind.JSDocComment)[0]) == null ? void 0 : _declaration$getDesce.getText(); return doc ? [doc] : []; } function getFileModuleSignature({ declaration }) { const filename = getFilename({ declaration }); const signature = `module '${filename}' {}`; return formatText(signature); } function isFunction(declaration) { return tsm__namespace.Node.isFunctionDeclaration(declaration) && (declaration.isAmbient() || declaration.isImplementation()); } function newFunction({ id, name, declaration, getSource, getType }) { const kind = exports.DeclarationKinds.FunctionDeclaration; const docs = getJSDocs({ declaration }); const source = getSource({ declaration }); const type = getType({ declaration }); const signature = getFunctionSignature({ name, type }); return { kind, id, name, docs, source, signature, type }; } function isFunctionExpression(declaration) { return tsm__namespace.Node.isVariableDeclaration(declaration) && hasFunctionLikeType(declaration); } function newFunctionExpression({ id, name, declaration, getSource, getType }) { const kind = exports.DeclarationKinds.FunctionDeclaration; const docs = getJSDocs({ declaration }); const source = getSource({ declaration }); const type = getType({ declaration }); const signature = getFunctionSignature({ name, type }); return { kind, id, name, docs, source, signature, type }; } function getFunctionSignature({ name, type }) { const signature = `${name}: ${type}`; return formatFunctionSignature(signature); } function getDeclarationName({ exportName, declaration }) { var _declaration$getName; if (exportName !== 'default' || tsm__namespace.Node.isExpression(declaration) || tsm__namespace.Node.isSourceFile(declaration)) { return exportName; } return (_declaration$getName = declaration.getName()) != null ? _declaration$getName : exportName; } function isInterface(declaration) { return tsm__namespace.Node.isInterfaceDeclaration(declaration); } function newInterface({ id, name, declaration, getSource, getType }) { const kind = exports.DeclarationKinds.InterfaceDeclaration; const docs = getJSDocs({ declaration }); const source = getSource({ declaration }); const signature = getWrapperSignature({ declaration }); const members = getInterfaceMembers({ interfaceID: id, interfaceDeclaration: declaration, getSource, getType }); return { kind, id, name, docs, source, signature, members }; } function getInterfaceMembers({ interfaceID, interfaceDeclaration, getSource, getType }) { const seenMethods = new Set(); const members = interfaceDeclaration.getMembers().flatMap((declaration, index) => { const name = getMemberName({ declaration }); const id = toID(interfaceID, getMemberID({ declaration, index })); if (isInternalDeclaration({ declaration, name })) { return []; } if (tsm__namespace.Node.isPropertySignature(declaration)) { return newProperty({ id, name, declaration, getSource }); } if (tsm__namespace.Node.isMethodSignature(declaration)) { // Skip overloaded methods if (seenMethods.has(id)) { return []; } seenMethods.add(id); return newMethod({ id, name, declaration, getSource, getType }); } if (tsm__namespace.Node.isConstructSignatureDeclaration(declaration)) { return newConstructSignature({ id, name, declaration, getSource }); } if (tsm__namespace.Node.isCallSignatureDeclaration(declaration)) { return newCallSignature({ id, name, declaration, getSource }); } return newIndexSignature({ id, name, declaration, getSource }); }).sort(sortByID); return { properties: members.filter(isInterfacePropertyDeclaration), methods: members.filter(isInterfaceMethodDeclaration), constructSignatures: members.filter(isInterfaceConstructSignatureDeclaration), callSignatures: members.filter(isInterfaceCallSignatureDeclaration), indexSignatures: members.filter(isInterfaceIndexSignatureDeclaration) }; } function getMemberName({ declaration }) { if (tsm__namespace.Node.isPropertySignature(declaration) || tsm__namespace.Node.isMethodSignature(declaration)) { return declaration.getName(); } if (tsm__namespace.Node.isConstructSignatureDeclaration(declaration)) { return 'construct signature'; } if (tsm__namespace.Node.isCallSignatureDeclaration(declaration)) { return 'call signature'; } return 'index signature'; } function getMemberID({ declaration, index }) { if (tsm__namespace.Node.isPropertySignature(declaration) || tsm__namespace.Node.isMethodSignature(declaration)) { return declaration.getName(); } if (tsm__namespace.Node.isConstructSignatureDeclaration(declaration)) { return `${index}-construct-signature`; } if (tsm__namespace.Node.isCallSignatureDeclaration(declaration)) { return `${index}-call-signature`; } return `${index}-index-signature`; } function newProperty({ id, name, declaration, getSource }) { const kind = exports.DeclarationKinds.InterfacePropertyDeclaration; const docs = getJSDocs({ declaration }); const source = getSource({ declaration }); const isReadonly = declaration.isReadonly(); const isOptional = declaration.hasQuestionToken(); const type = getApparentType({ declaration }); const signature = formatInterfaceMember(declaration.getText()); return { kind, id, name, docs, source, signature, isReadonly, isOptional, type }; } function newMethod({ id, name, declaration, getSource, getType }) { const kind = exports.DeclarationKinds.InterfaceMethodDeclaration; const docs = getJSDocs({ declaration }); const source = getSource({ declaration }); const type = getType({ declaration }); const signature = formatInterfaceMember(`${name}: ${type}`); return { kind, id, name, docs, source, signature, type }; } function newConstructSignature({ id, name, declaration, getSource }) { const kind = exports.DeclarationKinds.InterfaceConstructSignatureDeclaration; const docs = getJSDocs({ declaration }); const source = getSource({ declaration }); const signature = formatInterfaceMember(declaration.getText()); return { kind, id, name, docs, source, signature }; } function newCallSignature({ id, name, declaration, getSource }) { const kind = exports.DeclarationKinds.InterfaceCallSignatureDeclaration; const docs = getJSDocs({ declaration }); const source = getSource({ declaration }); const signature = formatInterfaceMember(declaration.getText()); return { kind, id, name, docs, source, signature }; } function newIndexSignature({ id, name, declaration, getSource }) { const kind = exports.DeclarationKinds.InterfaceIndexSignatureDeclaration; const docs = getJSDocs({ declaration }); const source = getSource({ declaration }); const signature = formatInterfaceMember(declaration.getText()); return { kind, id, name, docs, source, signature }; } function isExportedDeclarations(declaration) { return tsm__namespace.Node.isVariableDeclaration(declaration) || tsm__namespace.Node.isClassDeclaration(declaration) || tsm__namespace.Node.isInterfaceDeclaration(declaration) || tsm__namespace.Node.isEnumDeclaration(declaration) || tsm__namespace.Node.isTypeAliasDeclaration(declaration) || tsm__namespace.Node.isModuleDeclaration(declaration) || tsm__namespace.Node.isExpression(declaration) || tsm__namespace.Node.isFunctionDeclaration(declaration); } function isGlobalDeclaration({ declaration }) { const isGlobalVariable = tsm__namespace.Node.isVariableDeclaration(declaration) && declaration.getVariableStatementOrThrow().isAmbient() && !declaration.isExported(); const isGlobalFunction = tsm__namespace.Node.isFunctionDeclaration(declaration) && declaration.isAmbient() && declaration.getName() !== undefined && !declaration.isExported(); const isGlobalNamespace = tsm__namespace.Node.isModuleDeclaration(declaration) && declaration.isAmbient() && !declaration.isExported() && !declaration.hasModuleKeyword(); return isGlobalVariable || isGlobalFunction || isGlobalNamespace; } function isNamespace(declaration) { return tsm__namespace.Node.isModuleDeclaration(declaration); } function newNamespace({ id, name, declaration, declarations, getSource }) { const kind = exports.DeclarationKinds.NamespaceDeclaration; const docs = getJSDocs({ declaration }); const source = getSource({ declaration }); const signature = getNamespaceSignature({ id, name }); return { kind, id, name: id, docs, source, signature, declarations }; } function getNamespaceSignature({ id, name }) { const signature = isAmbientModule({ name }) ? `module ${name} {}` : `namespace ${id} {}`; return formatText(signature); } function isAmbientModule({ name }) { return name.startsWith("'") || name.startsWith('"'); } function isTypeAlias(declaration) { return tsm__namespace.Node.isTypeAliasDeclaration(declaration); } function newTypeAlias({ id, name, declaration, getSource }) { const kind = exports.DeclarationKinds.TypeAliasDeclaration; const docs = getJSDocs({ declaration }); const source = getSource({ declaration }); const signature = getTypeAliasSignature({ declaration }); return { kind, id, name, docs, source, signature }; } function getTypeAliasSignature({ declaration }) { const signature = declaration.getText(); return formatText(signature); } function getPackageDeclarations({ project, indexFile, getSource, getType, maxDepth = 5 }) { return getModuleDeclarations({ module: indexFile, moduleName: '', maxDepth, getSource, getType, project }); } /** * `getModuleDeclarations` extracts the public declarations from the given module. * * @param module - module (for example, a source file, node or namespace) * @param maxDepth - maximum extraction depth for inner modules * @param getSource - source provider * @param getType - type checker * @param moduleName - module's name, used to define IDs for declarations (optional) */ function getModuleDeclarations({ module, moduleName, maxDepth, getSource, getType, project }) { log('getModuleDeclarations: extracting declarations: %O', { moduleName, maxDepth, module }); const normalExportDeclarations = getNormalExportDeclarations({ module, moduleName }); log('getModuleDeclarations: got normal export declarations: %O', { moduleName, total: normalExportDeclarations.length, normalExportDeclarations }); const exportEqualsDeclarations = getExportEqualsDeclarations({ module, moduleName }); log('getModuleDeclarations: got export equals declarations: %O', { moduleName, total: exportEqualsDeclarations.length, exportEqualsDeclarations }); const ambientModulesDeclarations = getAmbientModulesDeclarations({ project }); log('getModuleDeclarations: got ambient modules declarations: %O', { moduleName, total: ambientModulesDeclarations.length, ambientModulesDeclarations }); const globalAmbientDeclarations = getGlobalAmbientDeclarations({ module, moduleName }); log('getModuleDeclarations: got global ambient declarations: %O', { moduleName, total: globalAmbientDeclarations.length, globalAmbientDeclarations }); return extractModuleDeclarations({ exportedDeclarations: [...normalExportDeclarations, ...exportEqualsDeclarations, ...ambientModulesDeclarations, ...globalAmbientDeclarations], maxDepth, getSource, getType }); } function getNormalExportDeclarations({ module, moduleName }) { const namedExports = new Set(); return Array.from(module.getExportedDeclarations()).flatMap(([exportName, declarations]) => { return declarations.flatMap(declaration => { // Skip internal/private declarations if (isInternalDeclaration({ declaration, name: exportName })) { return []; } const exportID = toID(moduleName, exportName); const declarationName = getDeclarationName({ exportName, declaration }); const declarationID = toID(moduleName, declarationName); // Keep track of named exports for the filter step if (declarationID === exportID) { namedExports.add(declarationID); } return { exportID, exportName, declarationID, declarationName, declaration }; }); }).filter(({ exportID, declarationID }) => { // Keep only named exports or default exports // with no corresponding named export return declarationID === exportID || !namedExports.has(declarationID); }); } function getExportEqualsDeclarations({ module, moduleName }) { var _module$getExportAssi; // Skip shorthand ambient modules without body // (for example, `declare module 'foo';`) if (tsm__namespace.Node.isModuleDeclaration(module) && !module.hasBody()) { return []; } const exportIdentifier = (_module$getExportAssi = module.getExportAssignment(ea => ea.isExportEquals())) == null ? void 0 : _module$getExportAssi.getLastChildByKind(tsm__namespace.SyntaxKind.Identifier); if (!exportIdentifier) { return []; } const exportName = exportIdentifier.getText(); return exportIdentifier.getDefinitionNodes().flatMap(declaration => { // Skip internal or unsupported declarations if (isInternalDeclaration({ declaration, name: exportName }) || !isExportedDeclarations(declaration)) { return []; } // Skip namespaces since `getNormalExportDeclarations` already extracts // the inner declarations of an export equals namespace // as non-namespaced declarations belonging to the parent module. // See snapshot for `export-equals-function-and-namespace.test.ts`. if (isNamespace(declaration)) { return []; } const exportID = toID(moduleName, exportName); const declarationName = getDeclarationName({ exportName, declaration }); const declarationID = toID(moduleName, declarationName); return { exportID, exportName, declarationID, declarationName, declaration }; }); } function getAmbientModulesDeclarations({ project }) { if (!project) { return []; } return project.getAmbientModules().flatMap(symbol => { return symbol.getDeclarations().flatMap(declaration => { const filepath = declaration.getSourceFile().getFilePath(); if (!tsm__namespace.Node.isModuleDeclaration(declaration) || filepath.startsWith('/node_modules')) { return []; } const exportName = declaration.getName(); const declarationName = exportName; // Remove surrounding quotes and eventual spaces const exportID = exportName.replace(/"|'/g, '').replace(/\s/g, '_').trim(); const declarationID = exportID; return { exportID, exportName, declarationID, declarationName, declaration }; }); }); } function getGlobalAmbientDeclarations({ module, moduleName }) { if (!tsm__namespace.Node.isSourceFile(module)) { return []; } // See https://www.typescriptlang.org/docs/handbook/declaration-files/by-example.html#global-variables const globalCandidates = [...module.getVariableDeclarations(), ...module.getFunctions(), ...module.getModules()]; return globalCandidates.flatMap(declaration => { // Global ambient functions must have a name const exportName = declaration.getName(); if (!isGlobalDeclaration({ declaration }) || isInternalDeclaration({ declaration, name: exportName })) { return []; } const exportID = toID(moduleName, exportName); const declarationName = getDeclarationName({ exportName, declaration }); const declarationID = toID(moduleName, declarationName); return { exportID, exportName, declarationID, declarationName, declaration }; }); } function extractModuleDeclarations({ exportedDeclarations, maxDepth, getSource, getType }) { const exportedFunctions = new Set(); const exportedNamespaces = new Set(); const declarations = exportedDeclarations.flatMap(({ exportID, declarationID: id, declarationName: name, declaration }) => { if (isVariable(declaration)) { return newVariable({ id, name, declaration, getSource }); } if (isVariableAssignmentExpression(declaration)) { return newVariableAssignmentExpression({ id, name, declaration, getSource }); } if (isExpression(declaration)) { return newExpression({ id, name, declaration, getSource }); } if (isFunction(declaration)) { // Skip ambient function overloads if (exportedFunctions.has(exportID)) { return []; } exportedFunctions.add(exportID); return newFunction({ id, name, declaration, getSource, getType }); } if (isFunctionExpression(declaration)) { return newFunctionExpression({ id, name, declaration, getSource, getType }); } if (isClass(declaration)) { return newClass({ id, name, declaration, getSource, getType }); } if (isInterface(declaration)) { return newInterface({ id, name, declaration, getSource, getType }); } if (isEnum(declaration)) { return newEnum({ id, name, declaration, getSource }); } if (isTypeAlias(declaration)) { return newTypeAlias({ id, name, declaration, getSource }); } if (isNamespace(declaration) && maxDepth > 0) { // Skip merged or nested namespace declarations if (exportedNamespaces.has(exportID)) { return []; } const declarations = getModuleDeclarations({ module: declaration, moduleName: id, maxDepth: maxDepth - 1, getSource, getType }); exportedNamespaces.add(exportID); return newNamespace({ id, name, declaration, declarations, getSource }); } // From `import * as ns from module; export { ns };` // or from `export * as ns from module`. if (isFileModule(declaration) && maxDepth > 0) { const declarations = getModuleDeclarations({ module: declaration, moduleName: id, maxDepth: maxDepth - 1, getSource, getType }); return newFileModule({ id, name, declaration, declarations, getSource }); } return []; }).sort(sortByID); return { variables: declarations.filter(isVariableDeclaration), functions: declarations.filter(isFunctionDeclaration), classes: declarations.filter(isClassDeclaration), interfaces: declarations.filter(isInterfaceDeclaration), enums: declarations.filter(isEnumDeclaration), typeAliases: declarations.filter(isTypeAliasDeclaration), namespaces: declarations.filter(isNamespaceDeclaration) }; } function getPackageFiles({ indexFile, declarations, getRepositoryFileURL, getUnpkgFileURL }) { const indexFilename = getFilename({ declaration: indexFile }); const declarationFilenames = getDeclarationFilenames({ declarations }); return Array.from(new Set([indexFilename, ...declarationFilenames])).sort().map(filename => { const url = getRepositoryFileURL({ filename }); const unpkgURL = getUnpkgFileURL({ filename }); if (filename === indexFilename) { return { isIndexFile: true, filename, url, unpkgURL }; } return { filename, url, unpkgURL }; }); } function getDeclarationFilenames({ declarations }) { return Object.values(declarations).flat().flatMap(declaration => { const { source: { filename } } = declaration; if (isNamespaceDeclaration(declaration)) { const { declarations } = declaration; return [filename, ...getDeclarationFilenames({ declarations })]; } return filename; }); } function getProject({ fileSystem, pattern = '**/*.ts' }) { const project = new tsm__namespace.Project({ fileSystem, compilerOptions: { // See https://github.com/dsherret/ts-morph/issues/938 // and https://github.com/microsoft/TypeScript/blob/master/lib/lib.esnext.full.d.ts lib: ['lib.esnext.full.d.ts'] } }); project.addSourceFilesAtPaths(pattern); return project; } function toForwardSlashes(s) { return s.replace(/\\/g, '/'); } function getRepositoryFileURLProvider({ repository }) { if (!repository) { return () => undefined; } const { url, tag = '', dir = '' } = repository; const info = HostedGitInfo__default["default"].fromUrl(url, { noGitPlus: true }); if (!info) { return () => undefined; } const linePrefix = getLinePrefix({ info }); return ({ filename, line }) => { const filepath = toForwardSlashes(path__namespace.join(dir, filename)); const fileURL = info.browse(filepath, { committish: tag }); const lineFragment = line ? `${linePrefix}${line}` : ''; return `${fileURL}${lineFragment}`; }; } function getLinePrefix({ info }) { const { type: provider } = info; switch (provider) { case 'bitbucket': return '#lines-'; default: return '#L'; } } function getStartLineNumber({ declaration }) { if (tsm__namespace.Node.isSourceFile(declaration)) { return 1; } return declaration.getStartLineNumber(); } function getSourceProvider({ getRepositoryFileURL, getUnpkgFileURL }) { return ({ declaration }) => { const filename = getFilename({ declaration }); const line = getStartLineNumber({ declaration }); const url = getRepositoryFileURL({ filename, line }); const unpkgURL = getUnpkgFileURL({ filename, line }); return { filename, line, url, unpkgURL }; }; } function getTypeChecker({ project }) { const projectTypeChecker = project.getTypeChecker(); return ({ declaration }) => { let type = 'any'; try { const typeChecker = projectTypeChecker.compilerObject; const { compilerNode } = declaration; const nodeType = typeChecker.getTypeAtLocation(compilerNode); type = typeChecker.typeToString(nodeType, compilerNode, tsm__namespace.ts.TypeFormatFlags.NoTruncation | tsm__namespace.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope); } catch (err) { // istanbul ignore next log('type checker: error: %O', { err }); } return type; }; } function getUnpkgFileURLProvider({ id }) { if (!id) { return () => undefined; } return ({ filename, line }) => { const fileURL = `https://unpkg.com/browse/${id}/${filename}`; if (!line) { return fileURL; } return `${fileURL}#L${line}`; }; } /** * `extractPackageAPI` extracts the public API from a package. * * @param fileSystem - filesystem containing the package's source code * @param entryPoint - absolute path of the file acting as the package's entry point * @param maxDepth - maximum depth for analyzing nested namespaces (default: `5`) * @param pattern - file pattern including files to be analyzed * @param repository - a tagged git repository to enable linking to source * @param id - npm-style package ID used for logging (for example, `foo@1.0.0`) * * @see {@link PackageAPI} */ function extractPackageAPI({ fileSystem, entryPoint, maxDepth, pattern, repository, id }) { const start = perf_hooks.performance.now(); log('extractPackageAPI: extracting API: %O', { id, entryPoint, maxDepth, pattern, repository, fileSystem }); const project = getProject({ fileSystem, pattern }); log('extractPackageAPI: created project: %O', { id, files: project.getSourceFiles().map(file => file.getFilePath()) }); const indexFile = project.getSourceFileOrThrow(entryPoint); log('extractPackageAPI: found index file: %O', { id, indexFile: indexFile.getFilePath() }); const getRepositoryFileURL = getRepositoryFileURLProvider({ repository }); log('extractPackageAPI: got repository file URL provider'); const getUnpkgFileURL = getUnpkgFileURLProvider({ id }); log('extractPackageAPI: got unpkg file URL provider'); const getSource = getSourceProvider({ getRepositoryFileURL, getUnpkgFileURL }); log('extractPackageAPI: got source provider'); const getType = getTypeChecker({ project }); log('extractPackageAPI: got type checker'); const overview = getOverview({ indexFile }); log('extractPackageAPI: