devghost
Version:
👻 Find dead code, dead imports, and dead dependencies before they haunt your project
223 lines • 7.58 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseFile = parseFile;
exports.extractImports = extractImports;
exports.extractExports = extractExports;
exports.isIdentifierUsed = isIdentifierUsed;
exports.getImportedPackages = getImportedPackages;
exports.getLineText = getLineText;
exports.getLineAndColumn = getLineAndColumn;
exports.getImportedIdentifiers = getImportedIdentifiers;
exports.isTypeOnlyImport = isTypeOnlyImport;
exports.isSideEffectImport = isSideEffectImport;
exports.createSourceFile = createSourceFile;
exports.shouldIgnoreFile = shouldIgnoreFile;
exports.shouldIgnoreLine = shouldIgnoreLine;
const fs = __importStar(require("node:fs"));
const ts = __importStar(require("typescript"));
/**
* Parse a TypeScript/JavaScript file using the TS Compiler API
*/
function parseFile(filePath) {
try {
const content = fs.readFileSync(filePath, 'utf-8');
return ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
}
catch (error) {
console.error(`Error parsing file ${filePath}:`, error);
return null;
}
}
/**
* Extract all import declarations from a source file
*/
function extractImports(sourceFile) {
const imports = [];
function visit(node) {
if (ts.isImportDeclaration(node)) {
imports.push(node);
}
ts.forEachChild(node, visit);
}
visit(sourceFile);
return imports;
}
/**
* Extract all export declarations from a source file
*/
function extractExports(sourceFile) {
const exports = [];
function visit(node) {
if (ts.isExportDeclaration(node)) {
exports.push(node);
}
ts.forEachChild(node, visit);
}
visit(sourceFile);
return exports;
}
/**
* Check if an identifier is used in the source file
*/
function isIdentifierUsed(sourceFile, identifierName) {
let isUsed = false;
function visit(node) {
// Skip import declarations themselves
if (ts.isImportDeclaration(node)) {
return;
}
// Check if this is an identifier with the name we're looking for
if (ts.isIdentifier(node) && node.text === identifierName) {
isUsed = true;
return;
}
ts.forEachChild(node, visit);
}
visit(sourceFile);
return isUsed;
}
/**
* Extract package names from all imports in a source file
*/
function getImportedPackages(sourceFile) {
const packages = new Set();
const imports = extractImports(sourceFile);
for (const importDecl of imports) {
if (ts.isStringLiteral(importDecl.moduleSpecifier)) {
const moduleName = importDecl.moduleSpecifier.text;
// Extract package name (handle scoped packages)
if (moduleName.startsWith('.') || moduleName.startsWith('/')) {
// Relative import, skip
continue;
}
// Extract the package name
let packageName;
if (moduleName.startsWith('@')) {
// Scoped package: @scope/package
const parts = moduleName.split('/');
packageName = parts.slice(0, 2).join('/');
}
else {
// Regular package: package or package/subpath
packageName = moduleName.split('/')[0];
}
packages.add(packageName);
}
}
return packages;
}
/**
* Get the text of a specific line in the source file
*/
function getLineText(sourceFile, lineNumber) {
const lines = sourceFile.text.split('\n');
return lines[lineNumber] || '';
}
/**
* Get line and column from a position in the source file
* Returns 1-indexed line numbers (line 1 = first line) for consistency
*/
function getLineAndColumn(sourceFile, pos) {
const { line, character } = sourceFile.getLineAndCharacterOfPosition(pos);
// TypeScript returns 0-indexed line numbers, convert to 1-indexed
return { line: line + 1, column: character };
}
/**
* Get all identifiers imported from an import declaration
*/
function getImportedIdentifiers(importDecl) {
const identifiers = [];
if (!importDecl.importClause) {
// Side-effect import: import './file'
return [];
}
const { importClause } = importDecl;
// Default import: import Foo from 'bar'
if (importClause.name) {
identifiers.push(importClause.name.text);
}
// Named imports: import { a, b } from 'bar'
if (importClause.namedBindings) {
if (ts.isNamedImports(importClause.namedBindings)) {
for (const element of importClause.namedBindings.elements) {
identifiers.push(element.name.text);
}
}
else if (ts.isNamespaceImport(importClause.namedBindings)) {
// Namespace import: import * as foo from 'bar'
identifiers.push(importClause.namedBindings.name.text);
}
}
return identifiers;
}
/**
* Check if an import is a type-only import
*/
function isTypeOnlyImport(importDecl) {
return importDecl.importClause?.isTypeOnly || false;
}
/**
* Check if an import is a side-effect import (no imported identifiers)
*/
function isSideEffectImport(importDecl) {
return !importDecl.importClause;
}
/**
* Create a source file from file path and content
*/
function createSourceFile(filePath, content) {
const fileContent = content || fs.readFileSync(filePath, 'utf-8');
return ts.createSourceFile(filePath, fileContent, ts.ScriptTarget.Latest, true);
}
/**
* Check if a file should be ignored based on devghost-ignore-file comment
*/
function shouldIgnoreFile(content) {
const firstLines = content.split('\n').slice(0, 10).join('\n');
return firstLines.includes('devghost-ignore-file');
}
/**
* Check if a line should be ignored based on devghost-ignore-next-line comment
*/
function shouldIgnoreLine(content, lineNumber) {
const lines = content.split('\n');
if (lineNumber > 0 && lineNumber <= lines.length) {
const previousLine = lines[lineNumber - 1];
return previousLine.includes('devghost-ignore-next-line');
}
return false;
}
//# sourceMappingURL=tsparser.js.map