UNPKG

detective-typescript

Version:
159 lines (127 loc) 3.87 kB
import parser from '@typescript-eslint/typescript-estree'; import { isRequire, isPlainRequire, isMainScopedRequire } from 'ast-module-types'; import Walker from 'node-source-walk'; /** * Extracts the dependencies of the supplied TypeScript module * * @param {String|Object} src - File's content or AST * @return {String[]} */ export default function detective(src, options = {}) { if (src === undefined) throw new Error('src not given'); if (src === '') return []; // Destructure detective-specific options; the rest are forwarded to the walker/parser. const { // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-9.html#import-types // https://www.typescriptlang.org/v2/docs/handbook/release-notes/typescript-3-8.html#type-only-imports-and-export skipTypeImports: skipTypeImportsRaw, mixedImports: mixedImportsRaw, skipAsyncImports, onFile, onAfterFile, ...walkerOptions } = options; const skipTypeImports = Boolean(skipTypeImportsRaw); const mixedImports = Boolean(mixedImportsRaw); walkerOptions.parser = parser; const walker = new Walker(walkerOptions); const dependencies = []; // Pre-parse the source to get the AST to pass to `onFile`, // then reuse that AST below in our walker walk. const ast = typeof src === 'string' ? walker.parse(src) : src; if (onFile) { onFile({ options, src, ast, walker }); } walker.walk(ast, node => { switch (node.type) { case 'ImportExpression': { if (!skipAsyncImports && node.source?.value) { dependencies.push(node.source.value); } break; } case 'ImportDeclaration': { if (skipTypeImports && isTypeNode(node, 'importKind')) { break; } if (node.source?.value) { dependencies.push(node.source.value); } break; } case 'ExportNamedDeclaration': case 'ExportAllDeclaration': { if (skipTypeImports && isTypeNode(node, 'exportKind')) { break; } if (node.source?.value) { dependencies.push(node.source.value); } break; } case 'TSExternalModuleReference': { if (node.expression?.value) { dependencies.push(node.expression.value); } break; } case 'TSImportType': { if (skipTypeImports) break; if (node.argument.type === 'TSLiteralType') { dependencies.push(node.argument.literal.value); } break; } case 'CallExpression': { const dep = handleCallExpression(node, mixedImports); if (dep) dependencies.push(dep); break; } default: // nothing } }); if (onAfterFile) { onAfterFile({ options, src, ast, walker, dependencies }); } return dependencies; } detective.tsx = function(src, options = {}) { return detective(src, { ...options, jsx: true }); }; function extractDependencyFromRequire(node) { if (['Literal', 'StringLiteral'].includes(node.arguments[0].type)) { return node.arguments[0].value; } if (node.arguments[0].type === 'TemplateLiteral') { return node.arguments[0].quasis[0].value.raw; } } function extractDependencyFromMainRequire(node) { return node.arguments[0].value; } function isTypeNode(node, kind) { return node[kind] === 'type' || (node.specifiers?.length > 0 && node.specifiers.every(n => n[kind] === 'type')); } function handleCallExpression(node, mixedImports) { if (!mixedImports || !isRequire(node) || !node.arguments || node.arguments.length === 0) { return; } if (isPlainRequire(node)) { return extractDependencyFromRequire(node); } if (isMainScopedRequire(node)) { return extractDependencyFromMainRequire(node); } }