UNPKG

knip

Version:

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

142 lines (141 loc) 5.57 kB
import { parseSync, rawTransferSupported, } from 'oxc-parser'; import { DEFAULT_EXTENSIONS, FIX_FLAGS, SYMBOL_TYPE } from "../../constants.js"; import { extname } from "../../util/path.js"; import { EMPTY_TAGS } from "./jsdoc.js"; const defaultParseOptions = { sourceType: 'unambiguous', experimentalRawTransfer: rawTransferSupported(), }; export const parseFile = (filePath, sourceText) => { const ext = extname(filePath); const parseFileName = DEFAULT_EXTENSIONS.has(ext) ? filePath : `${filePath}.ts`; return parseSync(parseFileName, sourceText, defaultParseOptions); }; export const buildLineStarts = (sourceText) => { const starts = [0]; for (let i = 0; i < sourceText.length; i++) { if (sourceText.charCodeAt(i) === 10) starts.push(i + 1); } return starts; }; export const getLineAndCol = (lineStarts, pos) => { let lo = 0; let hi = lineStarts.length - 1; while (lo < hi) { const mid = (lo + hi + 1) >> 1; if (lineStarts[mid] <= pos) lo = mid; else hi = mid - 1; } return { line: lo + 1, col: pos - lineStarts[lo] + 1 }; }; const isQuoteOrBacktick = (ch) => ch === 39 || ch === 34 || ch === 96; export const stripQuotes = (name) => { const length = name.length; if (length >= 2 && name.charCodeAt(0) === name.charCodeAt(length - 1) && isQuoteOrBacktick(name.charCodeAt(0))) return name.substring(1, length - 1); return name; }; export const isStringLiteral = (node) => node?.type === 'StringLiteral' || (node?.type === 'Literal' && typeof node.value === 'string') || (node?.type === 'TemplateLiteral' && node.quasis?.length === 1 && node.expressions?.length === 0); export const getStringValue = (node) => { if (node?.type === 'StringLiteral') return node.value; if (node?.type === 'Literal' && typeof node.value === 'string') return node.value; if (node?.type === 'TemplateLiteral' && node.quasis?.length === 1 && node.expressions?.length === 0) return node.quasis[0].value?.cooked ?? node.quasis[0].value?.raw; return undefined; }; export const shouldCountRefs = (ignoreExportsUsedInFile, type) => ignoreExportsUsedInFile === true || (typeof ignoreExportsUsedInFile === 'object' && type !== 'unknown' && ignoreExportsUsedInFile[type]); export function extractNamespaceMembers(decl, options, lineStarts, getJSDocTags, prefix) { if (!decl.body || decl.body.type !== 'TSModuleBlock') return []; const members = []; const addMember = (name, pos, stmtStart, stmtEnd) => { const fullName = prefix ? `${prefix}.${name}` : name; const tags = getJSDocTags(stmtStart); const existing = members.find(m => m.identifier === fullName); if (existing) { if (tags.size) { if (existing.jsDocTags === EMPTY_TAGS) existing.jsDocTags = new Set(tags); else for (const t of tags) existing.jsDocTags.add(t); } return; } const { line, col } = getLineAndCol(lineStarts, pos); const fix = options.isFixExports ? [stmtStart, stmtEnd, FIX_FLAGS.OBJECT_BINDING | FIX_FLAGS.WITH_NEWLINE] : undefined; members.push({ identifier: fullName, type: SYMBOL_TYPE.MEMBER, pos, line, col, fix, hasRefsInFile: false, jsDocTags: tags, flags: 0, }); }; for (const stmt of decl.body.body) { if (stmt.type !== 'ExportNamedDeclaration' || !stmt.declaration) continue; const d = stmt.declaration; if (d.type === 'VariableDeclaration') { for (const declarator of d.declarations) { if (declarator.id.type === 'Identifier') { addMember(declarator.id.name, declarator.id.start, stmt.start, stmt.end); } } } else if (d.type === 'TSModuleDeclaration' && d.kind !== 'global' && d.id.type === 'Identifier') { const nestedPrefix = prefix ? `${prefix}.${d.id.name}` : d.id.name; const nested = extractNamespaceMembers(d, options, lineStarts, getJSDocTags, nestedPrefix); for (const m of nested) members.push(m); } else if (d.id && 'name' in d.id) { addMember(d.id.name, d.id.start, stmt.start, stmt.end); } } return members; } export function extractEnumMembers(decl, options, lineStarts, getJSDocTags) { if (!decl.body?.members) return []; return decl.body.members.map((member) => { const name = member.id.type === 'Identifier' ? member.id.name : member.id.type === 'Literal' ? member.id.raw ? stripQuotes(member.id.raw) : member.id.value : ''; const pos = member.id.start; const { line, col } = getLineAndCol(lineStarts, pos); const fix = options.isFixExports ? [member.start, member.end, FIX_FLAGS.OBJECT_BINDING | FIX_FLAGS.WITH_NEWLINE] : undefined; const jsDocTags = getJSDocTags(member.start); return { identifier: name, type: SYMBOL_TYPE.MEMBER, pos, line, col, fix, hasRefsInFile: name === '', jsDocTags, flags: 0, }; }); }