UNPKG

knip

Version:

Find and fix unused dependencies, exports and files in your TypeScript and JavaScript projects

389 lines (388 loc) 20.8 kB
import { ALIAS_TAG, FIX_FLAGS, IMPORT_FLAGS, IMPORT_STAR, SYMBOL_TYPE } from "../../constants.js"; import { addNsValue, addValue } from "../../util/module-graph.js"; import { extractEnumMembers, extractNamespaceMembers, getLineAndCol, getStringValue, isStringLiteral, } from "./helpers.js"; import { EMPTY_TAGS } from "./jsdoc.js"; const getName = (n) => (n?.type === 'Identifier' ? n.name : undefined); export function handleExportNamed(node, s) { if (s.skipExports || s.isInNamespace(node)) return; if (node.source) { const declTags = s.getJSDocTags(node.start); for (const spec of node.specifiers) { const exportedName = getName(spec.exported) ?? getName(spec.local); if (exportedName) { const isType = node.exportKind === 'type' || spec.exportKind === 'type'; const type = isType ? SYMBOL_TYPE.TYPE : SYMBOL_TYPE.UNKNOWN; const fix = (s.options.isFixExports && !isType) || (s.options.isFixTypes && isType) ? [spec.start, spec.end, FIX_FLAGS.OBJECT_BINDING | FIX_FLAGS.EMPTY_DECLARATION] : undefined; const specTags = s.getJSDocTags(spec.start); const tags = specTags.size ? new Set([...declTags, ...specTags]) : declTags; s.addExport(exportedName, type, spec.exported?.start ?? spec.start, [], fix, true, tags); } } return; } const decl = node.declaration; if (decl) { const exportStart = node.start; if (decl.type === 'VariableDeclaration') { for (const declarator of decl.declarations) { if (declarator.id.type === 'ObjectPattern') { for (const p of declarator.id.properties) { if (p.type === 'RestElement' && p.argument?.type === 'Identifier') { const name = p.argument.name; const fix = s.options.isFixExports ? [p.start, p.end, FIX_FLAGS.OBJECT_BINDING] : undefined; s.addExport(name, SYMBOL_TYPE.UNKNOWN, p.argument.start, [], fix, false, s.getJSDocTags(exportStart)); s.destructuredExports.add(name); } else if (p.value?.type === 'Identifier') { const name = p.value.name; const fix = s.options.isFixExports ? [p.start, p.end, FIX_FLAGS.OBJECT_BINDING] : undefined; s.addExport(name, SYMBOL_TYPE.UNKNOWN, p.value.start, [], fix, false, s.getJSDocTags(exportStart)); s.destructuredExports.add(name); } else if (p.value?.type === 'AssignmentPattern' && p.value.left?.type === 'Identifier') { const name = p.value.left.name; const fix = s.options.isFixExports ? [p.start, p.end, FIX_FLAGS.OBJECT_BINDING] : undefined; s.addExport(name, SYMBOL_TYPE.UNKNOWN, p.value.left.start, [], fix, false, s.getJSDocTags(exportStart)); s.destructuredExports.add(name); } } } else if (declarator.id.type === 'ArrayPattern') { for (const el of declarator.id.elements) { if (el?.type === 'Identifier') { const fix = s.options.isFixExports ? [el.start, el.end, FIX_FLAGS.NONE] : undefined; s.addExport(el.name, SYMBOL_TYPE.UNKNOWN, el.start, [], fix, false, s.getJSDocTags(exportStart)); s.destructuredExports.add(el.name); } else if (el?.type === 'RestElement' && el.argument?.type === 'Identifier') { const fix = s.options.isFixExports ? [el.start, el.end, FIX_FLAGS.NONE] : undefined; s.addExport(el.argument.name, SYMBOL_TYPE.UNKNOWN, el.argument.start, [], fix, false, s.getJSDocTags(exportStart)); s.destructuredExports.add(el.argument.name); } } } else if (declarator.id.type === 'Identifier') { const name = declarator.id.name; const fix = s.getFix(exportStart, exportStart + 7); const jsDocTags = s.getJSDocTags(exportStart); let isReExport = false; if (declarator.init?.type === 'Identifier') { const _import = s.localImportMap.get(declarator.init.name); if (_import) { isReExport = true; const internalImport = s.internal.get(_import.filePath); if (internalImport) { if (_import.isNamespace) { addValue(internalImport.reExportNs, name, s.filePath); } else if (_import.importedName !== name) { addNsValue(internalImport.reExportAs, _import.importedName, name, s.filePath); } else { addValue(internalImport.reExport, _import.importedName, s.filePath); } } } } if (declarator.init?.type === 'ObjectExpression') { const findSpreads = (obj, path) => { for (const prop of obj.properties) { if (prop.type === 'SpreadElement' && prop.argument?.type === 'Identifier') { const _import = s.localImportMap.get(prop.argument.name); if (_import) { isReExport = true; const internalImport = s.internal.get(_import.filePath); if (internalImport) { addNsValue(internalImport.reExportAs, prop.argument.name, path.join('.'), s.filePath); } s.accessedAliases.add(name); } } else if (prop.type === 'Property' && prop.value?.type === 'ObjectExpression' && prop.key?.type === 'Identifier') { findSpreads(prop.value, [...path, prop.key.name]); } } }; findSpreads(declarator.init, [name]); } s.addExport(name, SYMBOL_TYPE.UNKNOWN, declarator.id.start, [], fix, isReExport, jsDocTags); if (!jsDocTags.has(ALIAS_TAG) && declarator.init?.type === 'Identifier') { const initName = declarator.init.name; const existingExport = s.exports.get(initName); if (existingExport && !existingExport.isReExport) { if (!s.aliasedExports.has(initName)) { s.aliasedExports.set(initName, [ { symbol: initName, pos: existingExport.pos, line: existingExport.line, col: existingExport.col }, ]); } const aliased = s.aliasedExports.get(initName); if (aliased) { const { line: l, col: c } = getLineAndCol(s.lineStarts, declarator.id.start); aliased.push({ symbol: name, pos: declarator.id.start, line: l, col: c }); } } } } } } else if ((decl.type === 'FunctionDeclaration' || decl.type === 'TSDeclareFunction') && decl.id) { const fix = s.getFix(exportStart, exportStart + 7); s.addExport(decl.id.name, SYMBOL_TYPE.FUNCTION, decl.id.start, [], fix, false, s.getJSDocTags(exportStart)); } else if (decl.type === 'ClassDeclaration' && decl.id) { const fix = s.getFix(exportStart, exportStart + 7); s.addExport(decl.id.name, SYMBOL_TYPE.CLASS, decl.id.start, [], fix, false, s.getJSDocTags(exportStart)); } else if (decl.type === 'TSTypeAliasDeclaration') { const fix = s.getTypeFix(exportStart, exportStart + 7); s.addExport(decl.id.name, SYMBOL_TYPE.TYPE, decl.id.start, [], fix, false, s.getJSDocTags(exportStart)); s.collectRefsInType(decl.typeAnnotation, decl.id.name, false); } else if (decl.type === 'TSInterfaceDeclaration') { const fix = s.getTypeFix(exportStart, exportStart + 7); s.addExport(decl.id.name, SYMBOL_TYPE.INTERFACE, decl.id.start, [], fix, false, s.getJSDocTags(exportStart)); s.collectRefsInType(decl.body, decl.id.name, false); for (const ext of decl.extends ?? []) { if (ext.expression?.type === 'Identifier') s.addRefInExport(ext.expression.name, decl.id.name); } } else if (decl.type === 'TSEnumDeclaration') { const members = extractEnumMembers(decl, s.options, s.lineStarts, s.getJSDocTags); const fix = s.getTypeFix(exportStart, exportStart + 7); s.addExport(decl.id.name, SYMBOL_TYPE.ENUM, decl.id.start, members, fix, false, s.getJSDocTags(exportStart)); } else if (decl.type === 'TSModuleDeclaration' && decl.kind !== 'global' && decl.id.type === 'Identifier') { const members = extractNamespaceMembers(decl, s.options, s.lineStarts, s.getJSDocTags); const fix = s.getFix(exportStart, exportStart + 7); s.addExport(decl.id.name, SYMBOL_TYPE.NAMESPACE, decl.id.start, members, fix, false, s.getJSDocTags(exportStart)); } } if (node.specifiers && !node.source) { for (const spec of node.specifiers) { const exportedName = getName(spec.exported) ?? getName(spec.local); const localName = getName(spec.local); const isType = node.exportKind === 'type' || spec.exportKind === 'type'; const type = isType ? SYMBOL_TYPE.TYPE : SYMBOL_TYPE.UNKNOWN; const fix = (s.options.isFixExports && !isType) || (s.options.isFixTypes && isType) ? [spec.start, spec.end, FIX_FLAGS.OBJECT_BINDING | FIX_FLAGS.EMPTY_DECLARATION] : undefined; const _import = localName ? s.localImportMap.get(localName) : undefined; const isReExport = !!_import; if (_import) { const internalImport = s.internal.get(_import.filePath); if (internalImport) { if (_import.isNamespace) { addValue(internalImport.reExportNs, exportedName, s.filePath); } else if (_import.importedName !== exportedName) { addNsValue(internalImport.reExportAs, _import.importedName, exportedName, s.filePath); } else { addValue(internalImport.reExport, _import.importedName, s.filePath); } } } s.addExport(exportedName, type, spec.exported?.start ?? spec.start, [], fix, isReExport, s.getJSDocTags(node.start)); if (exportedName) s.specifierExportNames.add(exportedName); } } } export function handleExportDefault(node, s) { if (s.skipExports || s.isInNamespace(node)) return; const decl = node.declaration; const hasDeclarationBody = decl.type === 'ClassDeclaration' || decl.type === 'FunctionDeclaration'; const fix = s.options.isFixExports ? hasDeclarationBody ? [node.start, decl.start, FIX_FLAGS.NONE] : [node.start, node.end + 1, FIX_FLAGS.NONE] : undefined; let type = SYMBOL_TYPE.UNKNOWN; let pos = decl.start; let members = []; if (decl.type === 'FunctionDeclaration') { type = SYMBOL_TYPE.FUNCTION; pos = decl.id?.start ?? decl.start; } else if (decl.type === 'ClassDeclaration') { type = SYMBOL_TYPE.CLASS; pos = decl.id?.start ?? decl.start; members = []; } else if (decl.type === 'TSInterfaceDeclaration') { type = SYMBOL_TYPE.INTERFACE; pos = decl.id.start; s.collectRefsInType(decl.body, 'default', false); } else if (decl.type === 'Identifier') { type = s.localDeclarationTypes.get(decl.name) ?? SYMBOL_TYPE.UNKNOWN; pos = decl.start; const _import = s.localImportMap.get(decl.name); if (_import) { const internalImport = s.internal.get(_import.filePath); if (internalImport) { if (_import.importedName !== 'default') { addNsValue(internalImport.reExportAs, _import.importedName, 'default', s.filePath); } else { addValue(internalImport.reExport, 'default', s.filePath); } } } const jsDocTags = s.getJSDocTags(node.start); if (!jsDocTags.has(ALIAS_TAG) && !s.specifierExportNames.has(decl.name)) { const existingExport = s.exports.get(decl.name); if (existingExport) { if (!s.aliasedExports.has(decl.name)) { s.aliasedExports.set(decl.name, [ { symbol: decl.name, pos: existingExport.pos, line: existingExport.line, col: existingExport.col }, ]); } const aliased = s.aliasedExports.get(decl.name); if (aliased) { const { line: defLine, col: defCol } = getLineAndCol(s.lineStarts, decl.start); aliased.push({ symbol: 'default', pos: decl.start, line: defLine, col: defCol }); } } } } s.addExport('default', type, pos, members, fix, false, s.getJSDocTags(node.start)); } export function handleExportAssignment(node, s) { if (s.skipExports || s.isInNamespace(node)) return; const expr = node.expression; if (expr.type === 'Identifier') { const _import = s.localImportMap.get(expr.name); if (_import) { const internalImport = s.internal.get(_import.filePath); if (internalImport) { addNsValue(internalImport.reExportAs, expr.name, 'default', s.filePath); internalImport.refs.add(expr.name); } s.addExport('default', SYMBOL_TYPE.UNKNOWN, expr.start, [], undefined, true, s.getJSDocTags(node.start)); } else { s.addExport('default', SYMBOL_TYPE.UNKNOWN, expr.start, [], undefined, false, s.getJSDocTags(node.start)); } } else { s.addExport('default', SYMBOL_TYPE.UNKNOWN, expr.start, [], undefined, false, s.getJSDocTags(node.start)); } } export function handleExpressionStatement(node, s) { if (node.expression.type === 'Identifier') { if (s.destructuredExports.has(node.expression.name)) s.bareExprRefs.add(node.expression.name); } if (!s.isJS) return; const expr = node.expression; if (expr.type !== 'AssignmentExpression' || expr.operator !== '=') return; const left = expr.left; if (left.type !== 'MemberExpression') return; if (!left.computed && left.object.type === 'Identifier' && left.object.name === 'exports') { if (s.skipExports) return; const name = left.property.name; if (name) { const fix = s.options.isFixExports ? [node.start, node.end, FIX_FLAGS.NONE] : undefined; s.addExport(name, SYMBOL_TYPE.UNKNOWN, left.property.start, [], fix, false, EMPTY_TAGS); } return; } if (left.object.type === 'MemberExpression' && !left.object.computed && left.object.object.type === 'Identifier' && left.object.object.name === 'module' && left.object.property.name === 'exports') { let exportName; if (!left.computed && left.property.type === 'Identifier') { exportName = left.property.name; } else if (left.computed && isStringLiteral(left.property)) { exportName = getStringValue(left.property); } if (exportName) { const right = expr.right; let isReExport = false; if (right.type === 'MemberExpression' && right.object.type === 'CallExpression' && right.object.callee.type === 'Identifier' && right.object.callee.name === 'require' && right.object.arguments.length === 1 && isStringLiteral(right.object.arguments[0])) { const specifier = getStringValue(right.object.arguments[0]); let memberName; if (!right.computed && right.property.type === 'Identifier') memberName = right.property.name; else if (right.computed && isStringLiteral(right.property)) memberName = getStringValue(right.property); if (memberName) { const alias = exportName !== memberName ? exportName : undefined; s.addImport(specifier, memberName, alias, undefined, right.object.arguments[0].start, IMPORT_FLAGS.RE_EXPORT); isReExport = true; } } if (!s.skipExports) { const fix = s.options.isFixExports ? [node.start, node.end, FIX_FLAGS.NONE] : undefined; s.addExport(exportName, SYMBOL_TYPE.UNKNOWN, left.property.start, [], fix, isReExport, EMPTY_TAGS); } } return; } if (!left.computed && left.object.type === 'Identifier' && left.object.name === 'module' && left.property.name === 'exports') { const right = expr.right; if (right.type === 'ObjectExpression') { if (s.skipExports) return; const props = right.properties; const allShorthand = props.length > 0 && props.every(p => p.type === 'Property' && p.shorthand); if (allShorthand) { for (const prop of props) { if (prop.type === 'Property' && prop.key.type === 'Identifier') { const fix = s.options.isFixExports ? [prop.start, prop.end, FIX_FLAGS.NONE] : undefined; s.addExport(prop.key.name, SYMBOL_TYPE.UNKNOWN, prop.key.start, [], fix, false, EMPTY_TAGS); } } } else { s.addExport('default', SYMBOL_TYPE.UNKNOWN, right.start, [], undefined, false, EMPTY_TAGS); } for (const prop of props) { if (prop.type === 'SpreadElement' && prop.argument.type === 'CallExpression' && prop.argument.callee.type === 'Identifier' && prop.argument.callee.name === 'require' && isStringLiteral(prop.argument.arguments[0])) { const specifier = getStringValue(prop.argument.arguments[0]); s.addImport(specifier, IMPORT_STAR, undefined, undefined, prop.argument.arguments[0].start, IMPORT_FLAGS.RE_EXPORT); } } } else if (right.type === 'CallExpression' && right.callee.type === 'Identifier' && right.callee.name === 'require') { if (isStringLiteral(right.arguments[0])) { const specifier = getStringValue(right.arguments[0]); s.addImport(specifier, IMPORT_STAR, undefined, undefined, right.arguments[0].start, IMPORT_FLAGS.RE_EXPORT); } } else if (!s.skipExports) { s.addExport('default', SYMBOL_TYPE.UNKNOWN, right.start, [], undefined, false, EMPTY_TAGS); } return; } }