Socket
Socket
Sign inDemoInstall

knip

Package Overview
Dependencies
Maintainers
1
Versions
407
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

knip - npm Package Compare versions

Comparing version 0.7.3 to 0.8.0

license

4

dist/cli.js

@@ -13,3 +13,3 @@ #!/usr/bin/env node

const errors_1 = require("./util/errors");
const { values: { help, dir, config: configFilePath = 'knip.json', tsConfig: tsConfigFilePath, include = [], exclude = [], ignore = [], 'no-gitignore': isNoGitIgnore = false, dev: isDev = false, 'no-progress': noProgress = false, reporter = 'symbols', 'reporter-options': reporterOptions = '', 'max-issues': maxIssues = '0', jsdoc: jsDoc = [], debug: isDebug = false, 'debug-level': debugLevel = '1', }, } = (0, node_util_1.parseArgs)({
const { values: { help, dir, config: configFilePath, tsConfig: tsConfigFilePath, include = [], exclude = [], ignore = [], 'no-gitignore': isNoGitIgnore = false, dev: isDev = false, 'include-entry-files': isIncludeEntryFiles = false, 'no-progress': noProgress = false, reporter = 'symbols', 'reporter-options': reporterOptions = '', 'max-issues': maxIssues = '0', jsdoc: jsDoc = [], debug: isDebug = false, 'debug-level': debugLevel = '1', }, } = (0, node_util_1.parseArgs)({
options: {

@@ -25,2 +25,3 @@ help: { type: 'boolean' },

dev: { type: 'boolean' },
'include-entry-files': { type: 'boolean' },
'no-progress': { type: 'boolean' },

@@ -54,2 +55,3 @@ 'max-issues': { type: 'string' },

gitignore: !isNoGitIgnore,
isIncludeEntryFiles,
isDev,

@@ -56,0 +58,0 @@ isShowProgress,

@@ -16,2 +16,3 @@ "use strict";

--dev Include \`devDependencies\` in report(s)
--include-entry-files Report unused exports and types for entry files
--no-progress Don't show dynamic progress updates

@@ -18,0 +19,0 @@ --max-issues Maximum number of issues before non-zero exit code (default: 0)

@@ -17,59 +17,73 @@ "use strict";

const main = async (options) => {
const { cwd, workingDir, configFilePath = 'knip.json', tsConfigFilePath, include, exclude, ignore, gitignore, isDev, isShowProgress, jsDoc, debug, } = options;
const { cwd, workingDir, configFilePath: configFilePathArg, tsConfigFilePath: tsConfigFilePathArg, include, exclude, ignore, gitignore, isIncludeEntryFiles, isDev, isShowProgress, jsDoc, debug, } = options;
(0, debug_1.debugLogObject)(options, 1, 'Unresolved onfiguration', options);
const localConfigurationPath = configFilePath && (await (0, fs_1.findFile)(workingDir, configFilePath));
const manifestPath = await (0, fs_1.findFile)(workingDir, 'package.json');
const localConfiguration = localConfigurationPath && require(localConfigurationPath);
const manifestPath = await (0, fs_1.findFile)(cwd, workingDir, 'package.json');
const manifest = manifestPath && require(manifestPath);
if (!localConfigurationPath && !manifest.knip) {
const location = workingDir === cwd ? 'current directory' : `${node_path_1.default.relative(cwd, workingDir)} or up.`;
throw new errors_1.ConfigurationError(`Unable to find ${configFilePath} or package.json#knip in ${location}`);
const configFilePath = configFilePathArg ?? 'knip.json';
const resolvedConfigFilePath = await (0, fs_1.findFile)(cwd, workingDir, configFilePath);
const localConfig = resolvedConfigFilePath && require(resolvedConfigFilePath);
if (configFilePathArg && !resolvedConfigFilePath) {
throw new errors_1.ConfigurationError(`Unable to find ${configFilePathArg}`);
}
const dir = node_path_1.default.relative(cwd, workingDir);
const resolvedConfig = (0, config_1.resolveConfig)(manifest.knip ?? localConfiguration, { workingDir: dir, isDev });
(0, debug_1.debugLogObject)(options, 1, 'Resolved onfiguration', resolvedConfig);
if (!resolvedConfig) {
throw new errors_1.ConfigurationError('Unable to find `entryFiles` and/or `projectFiles` in configuration.');
const tsConfigFilePath = tsConfigFilePathArg ?? 'tsconfig.json';
const resolvedTsConfigFilePath = await (0, fs_1.findFile)(cwd, workingDir, tsConfigFilePath);
if (tsConfigFilePathArg && !resolvedTsConfigFilePath) {
throw new errors_1.ConfigurationError(`Unable to find ${tsConfigFilePathArg}`);
}
const report = (0, config_1.resolveIncludedIssueGroups)(include, exclude, resolvedConfig);
let tsConfigPaths = [];
const tsConfigPath = await (0, fs_1.findFile)(workingDir, tsConfigFilePath ?? 'tsconfig.json');
if (tsConfigFilePath && !tsConfigPath) {
throw new errors_1.ConfigurationError(`Unable to find ${tsConfigFilePath}`);
}
if (tsConfigPath) {
const tsConfig = typescript_1.default.readConfigFile(tsConfigPath, typescript_1.default.sys.readFile);
tsConfigPaths = tsConfig.config.compilerOptions?.paths
? Object.keys(tsConfig.config.compilerOptions.paths).map(p => p.replace(/\*/g, '**'))
if (resolvedTsConfigFilePath) {
const config = typescript_1.default.readConfigFile(resolvedTsConfigFilePath, typescript_1.default.sys.readFile);
tsConfigPaths = config.config.compilerOptions?.paths
? Object.keys(config.config.compilerOptions.paths).map(p => p.replace(/\*/g, '**'))
: [];
if (tsConfig.error) {
throw new errors_1.ConfigurationError(`An error occured when reading ${node_path_1.default.relative(cwd, tsConfigPath)}`);
if (config.error) {
throw new errors_1.ConfigurationError(`Unable to read ${node_path_1.default.relative(cwd, resolvedTsConfigFilePath)}`);
}
}
const projectOptions = tsConfigPath ? { tsConfigFilePath: tsConfigPath } : { compilerOptions: { allowJs: true } };
const entryPaths = await (0, path_1.resolvePaths)({
cwd,
workingDir,
patterns: resolvedConfig.entryFiles,
ignore,
gitignore,
});
(0, debug_1.debugLogFiles)(options, 1, 'Globbed entry paths', entryPaths);
const production = (0, project_1.createProject)({ projectOptions, paths: entryPaths });
const entryFiles = production.getSourceFiles();
(0, debug_1.debugLogSourceFiles)(options, 1, 'Included entry source files', entryFiles);
production.resolveSourceFileDependencies();
const productionFiles = production.getSourceFiles();
(0, debug_1.debugLogSourceFiles)(options, 1, 'Included production source files', productionFiles);
const projectPaths = await (0, path_1.resolvePaths)({
cwd,
workingDir,
patterns: resolvedConfig.projectFiles,
ignore,
gitignore,
});
(0, debug_1.debugLogFiles)(options, 1, 'Globbed project paths', projectPaths);
const project = (0, project_1.createProject)({ projectOptions, paths: projectPaths });
const projectFiles = project.getSourceFiles();
(0, debug_1.debugLogSourceFiles)(options, 1, 'Included project source files', projectFiles);
const dir = node_path_1.default.relative(cwd, workingDir);
const resolvedConfig = (0, config_1.resolveConfig)(manifest.knip ?? localConfig, { workingDir: dir, isDev });
(0, debug_1.debugLogObject)(options, 1, 'Resolved onfiguration', resolvedConfig);
if (!resolvedConfigFilePath && !manifest.knip && !resolvedTsConfigFilePath) {
throw new errors_1.ConfigurationError(`Unable to find ${configFilePath} or package.json#knip or ${tsConfigFilePath}`);
}
const { entryFiles, productionFiles, projectFiles } = await (async () => {
if (resolvedConfig) {
const skipAddFiles = { skipAddingFilesFromTsConfig: true, skipFileDependencyResolution: true };
const projectOptions = resolvedTsConfigFilePath
? { tsConfigFilePath: resolvedTsConfigFilePath }
: { compilerOptions: { allowJs: true } };
const entryPaths = await (0, path_1.resolvePaths)({
cwd,
workingDir,
patterns: resolvedConfig.entryFiles,
ignore,
gitignore,
});
(0, debug_1.debugLogFiles)(options, 1, 'Globbed entry paths', entryPaths);
const production = (0, project_1.createProject)({ ...projectOptions, ...skipAddFiles }, entryPaths);
const entryFiles = production.getSourceFiles();
(0, debug_1.debugLogSourceFiles)(options, 1, 'Included entry source files', entryFiles);
production.resolveSourceFileDependencies();
const productionFiles = production.getSourceFiles();
(0, debug_1.debugLogSourceFiles)(options, 1, 'Included production source files', productionFiles);
const projectPaths = await (0, path_1.resolvePaths)({
cwd,
workingDir,
patterns: resolvedConfig.projectFiles,
ignore,
gitignore,
});
(0, debug_1.debugLogFiles)(options, 1, 'Globbed project paths', projectPaths);
const project = (0, project_1.createProject)({ ...projectOptions, ...skipAddFiles }, projectPaths);
const projectFiles = project.getSourceFiles();
(0, debug_1.debugLogSourceFiles)(options, 1, 'Included project source files', projectFiles);
return { entryFiles, productionFiles, projectFiles };
}
else {
const project = (0, project_1.createProject)({ tsConfigFilePath: resolvedTsConfigFilePath });
const files = project.getSourceFiles();
return { entryFiles: files, productionFiles: files, projectFiles: files };
}
})();
const report = (0, config_1.resolveIncludedIssueGroups)(include, resolvedConfig ? exclude : ['files'], resolvedConfig);
const config = {

@@ -81,2 +95,3 @@ workingDir,

projectFiles,
isIncludeEntryFiles: !resolvedConfig || isIncludeEntryFiles,
dependencies: Object.keys(manifest.dependencies ?? {}),

@@ -86,3 +101,3 @@ peerDependencies: Object.keys(manifest.peerDependencies ?? {}),

devDependencies: Object.keys(manifest.devDependencies ?? {}),
isDev: typeof resolvedConfig.dev === 'boolean' ? resolvedConfig.dev : isDev,
isDev: typeof resolvedConfig?.dev === 'boolean' ? resolvedConfig.dev : isDev,
tsConfigPaths,

@@ -89,0 +104,0 @@ isShowProgress,

@@ -18,3 +18,3 @@ "use strict";

const { workingDir, isShowProgress, report, isDev, jsDocOptions } = configuration;
const { entryFiles, productionFiles, projectFiles } = configuration;
const { entryFiles, productionFiles, projectFiles, isIncludeEntryFiles } = configuration;
const { getUnresolvedDependencies, getUnusedDependencies, getUnusedDevDependencies } = (0, dependencies_1.getDependencyAnalyzer)(configuration);

@@ -85,9 +85,2 @@ const [usedProductionFiles, unreferencedProductionFiles] = (0, project_1.partitionSourceFiles)(projectFiles, productionFiles);

};
if (report.dependencies || report.unlisted) {
usedEntryFiles.forEach(sourceFile => {
counters.processed++;
const unresolvedDependencies = getUnresolvedDependencies(sourceFile);
unresolvedDependencies.forEach(issue => addSymbolIssue('unresolved', issue));
});
}
if (report.dependencies ||

@@ -100,3 +93,3 @@ report.unlisted ||

report.duplicates) {
usedNonEntryFiles.forEach(sourceFile => {
usedProductionFiles.forEach(sourceFile => {
counters.processed++;

@@ -116,6 +109,10 @@ const filePath = sourceFile.getFilePath();

}
if (!isIncludeEntryFiles && entryFiles.includes(sourceFile))
return;
if (report.exports || report.types || report.nsExports || report.nsTypes) {
const uniqueExportedSymbols = new Set([...exportDeclarations.values()].flat());
if (uniqueExportedSymbols.size === 1)
return;
if (!isIncludeEntryFiles) {
const uniqueExportedSymbols = new Set([...exportDeclarations.values()].flat());
if (uniqueExportedSymbols.size === 1)
return;
}
exportDeclarations.forEach(declarations => {

@@ -133,6 +130,9 @@ declarations.forEach(declaration => {

let identifier;
let fakeIdentifier;
if (declaration.isKind(ts_morph_1.ts.SyntaxKind.Identifier)) {
identifier = declaration;
}
else if (declaration.isKind(ts_morph_1.ts.SyntaxKind.ArrowFunction)) {
else if (declaration.isKind(ts_morph_1.ts.SyntaxKind.ArrowFunction) ||
declaration.isKind(ts_morph_1.ts.SyntaxKind.ObjectLiteralExpression)) {
fakeIdentifier = 'default';
}

@@ -152,4 +152,4 @@ else if (declaration.isKind(ts_morph_1.ts.SyntaxKind.FunctionDeclaration) ||

}
if (identifier) {
const identifierText = identifier.getText();
if (identifier || fakeIdentifier) {
const identifierText = fakeIdentifier ?? identifier?.getText() ?? '*';
if (report.exports && issues.exports[filePath]?.[identifierText])

@@ -163,3 +163,3 @@ return;

return;
const refs = identifier.findReferences();
const refs = identifier?.findReferences() ?? [];
if (refs.length === 0) {

@@ -166,0 +166,0 @@ addSymbolIssue('exports', { filePath, symbol: identifierText });

@@ -47,2 +47,3 @@ import { SourceFile } from 'ts-morph';

gitignore: boolean;
isIncludeEntryFiles: boolean;
isDev: boolean;

@@ -65,2 +66,3 @@ isShowProgress: boolean;

entryFiles: SourceFile[];
isIncludeEntryFiles: boolean;
dependencies: string[];

@@ -67,0 +69,0 @@ peerDependencies: string[];

@@ -9,2 +9,4 @@ "use strict";

const resolveConfig = (importedConfiguration, options) => {
if (!importedConfiguration)
return;
let resolvedConfig = importedConfiguration;

@@ -11,0 +13,0 @@ const { workingDir, isDev } = options ?? {};

@@ -1,1 +0,1 @@

export declare const findFile: (cwd: string, fileName: string) => Promise<string | undefined>;
export declare const findFile: (cwd: string, workingDir: string, fileName: string) => Promise<string | undefined>;

@@ -18,4 +18,4 @@ "use strict";

};
const findFile = async (cwd, fileName) => {
const filePath = node_path_1.default.join(cwd, fileName);
const findFile = async (cwd, workingDir, fileName) => {
const filePath = node_path_1.default.join(workingDir, fileName);
if (await isFile(filePath)) {

@@ -25,6 +25,8 @@ return filePath;

else {
const parentDir = node_path_1.default.resolve(cwd, '..');
return parentDir === '/' ? undefined : (0, exports.findFile)(parentDir, fileName);
if (cwd === workingDir)
return;
const parentDir = node_path_1.default.resolve(workingDir, '..');
return (0, exports.findFile)(cwd, parentDir, fileName);
}
};
exports.findFile = findFile;
import { Project } from 'ts-morph';
import type { ProjectOptions, SourceFile } from 'ts-morph';
export declare const createProject: ({ projectOptions, paths }: {
projectOptions: ProjectOptions;
paths?: string[] | undefined;
}) => Project;
export declare const createProject: (projectOptions: ProjectOptions, paths?: string[]) => Project;
export declare const partitionSourceFiles: (projectFiles: SourceFile[], productionFiles: SourceFile[]) => SourceFile[][];

@@ -5,8 +5,4 @@ "use strict";

const ts_morph_1 = require("ts-morph");
const createProject = ({ projectOptions, paths }) => {
const workspace = new ts_morph_1.Project({
...projectOptions,
skipAddingFilesFromTsConfig: true,
skipFileDependencyResolution: true,
});
const createProject = (projectOptions, paths) => {
const workspace = new ts_morph_1.Project(projectOptions);
if (paths)

@@ -13,0 +9,0 @@ workspace.addSourceFilesAtPaths(paths);

{
"name": "knip",
"version": "0.7.3",
"version": "0.8.0",
"description": "Find unused files, dependencies and exports in your TypeScript and JavaScript project",

@@ -5,0 +5,0 @@ "keywords": [

@@ -212,2 +212,7 @@ # ✂️ Knip

## Zero-config
Knip can work without any configuration. Then an existing `tsconfig.json` file is required. Since `entryFiles` and
`projectFiles` are now the same, Knip is unable to report unused files.
## More configuration examples

@@ -438,3 +443,3 @@

[2]: #custom-reporters
[3]: #why-yet-another-unused-filedependencyexport-finder
[3]: #really-another-unused-filedependencyexport-finder
[4]: https://labs.openai.com/s/xZQACaLepaKya0PRUPtIN5dC

@@ -441,0 +446,0 @@ [5]: ./assets/cow-with-orange-scissors-van-gogh-style.webp

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc