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.9.1 to 0.10.0

2

dist/index.d.ts
import type { UnresolvedConfiguration } from './types';
export declare const main: (options: UnresolvedConfiguration) => Promise<{
export declare const main: (unresolvedConfiguration: UnresolvedConfiguration) => Promise<{
report: import("./types").Report;

@@ -4,0 +4,0 @@ issues: import("./types").Issues;

@@ -16,6 +16,6 @@ "use strict";

const progress_1 = require("./progress");
const main = async (options) => {
const { cwd, workingDir, configFilePath: configFilePathArg, tsConfigFilePath: tsConfigFilePathArg, include, exclude, ignore, gitignore, isIncludeEntryFiles, isDev, isShowProgress, jsDoc, debug, } = options;
const updateMessage = (0, progress_1.getMessageUpdater)(options);
(0, debug_1.debugLogObject)(options, 1, 'Unresolved configuration', options);
const main = async (unresolvedConfiguration) => {
const { cwd, workingDir, configFilePath: configFilePathArg, tsConfigFilePath: tsConfigFilePathArg, include, exclude, ignore, gitignore, isIncludeEntryFiles, isDev, isShowProgress, jsDoc, debug, } = unresolvedConfiguration;
const updateMessage = (0, progress_1.getMessageUpdater)(unresolvedConfiguration);
(0, debug_1.debugLogObject)(debug, 1, 'Unresolved configuration', unresolvedConfiguration);
updateMessage('Reading configuration and manifest files...');

@@ -35,6 +35,6 @@ const manifestPath = await (0, fs_1.findFile)(cwd, workingDir, 'package.json');

}
let tsConfigPaths = [];
let tsConfigPathGlobs = [];
if (resolvedTsConfigFilePath) {
const config = typescript_1.default.readConfigFile(resolvedTsConfigFilePath, typescript_1.default.sys.readFile);
tsConfigPaths = config.config.compilerOptions?.paths
tsConfigPathGlobs = config.config.compilerOptions?.paths
? Object.keys(config.config.compilerOptions.paths).map(p => p.replace(/\*/g, '**'))

@@ -48,3 +48,3 @@ : [];

const resolvedConfig = (0, config_1.resolveConfig)(manifest.knip ?? localConfig, { workingDir: dir, isDev });
(0, debug_1.debugLogObject)(options, 1, 'Resolved configuration', resolvedConfig);
(0, debug_1.debugLogObject)(debug, 1, 'Resolved configuration', resolvedConfig);
if (!resolvedConfigFilePath && !manifest.knip && !resolvedTsConfigFilePath) {

@@ -61,2 +61,3 @@ throw new errors_1.ConfigurationError(`Unable to find ${configFilePath} or package.json#knip or ${tsConfigFilePath}`);

const entryPaths = await (0, path_1.resolvePaths)({
cwd,
workingDir,

@@ -67,11 +68,12 @@ patterns: resolvedConfig.entryFiles,

});
(0, debug_1.debugLogFiles)(options, 1, 'Globbed entry paths', entryPaths);
(0, debug_1.debugLogFiles)(debug, 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);
(0, debug_1.debugLogSourceFiles)(debug, 1, 'Included entry source files', entryFiles);
production.resolveSourceFileDependencies();
const productionFiles = production.getSourceFiles();
(0, debug_1.debugLogSourceFiles)(options, 1, 'Included production source files', productionFiles);
(0, debug_1.debugLogSourceFiles)(debug, 1, 'Included production source files', productionFiles);
updateMessage('Resolving project files...');
const projectPaths = await (0, path_1.resolvePaths)({
cwd,
workingDir,

@@ -82,6 +84,6 @@ patterns: resolvedConfig.projectFiles,

});
(0, debug_1.debugLogFiles)(options, 1, 'Globbed project paths', projectPaths);
(0, debug_1.debugLogFiles)(debug, 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);
(0, debug_1.debugLogSourceFiles)(debug, 1, 'Included project source files', projectFiles);
return { entryFiles, productionFiles, projectFiles };

@@ -109,3 +111,3 @@ }

isDev: Boolean(resolvedConfig?.dev),
tsConfigPaths,
tsConfigPathGlobs: tsConfigPathGlobs,
isShowProgress,

@@ -118,5 +120,5 @@ jsDocOptions: {

const { issues, counters } = await (0, runner_1.findIssues)(config);
(0, debug_1.debugLogObject)(options, 2, 'Issues', issues);
(0, debug_1.debugLogObject)(debug, 2, 'Issues', issues);
return { report, issues, counters };
};
exports.main = main;

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

async function findIssues(configuration) {
const { workingDir, report, isDev, jsDocOptions } = configuration;
const { workingDir, report, isDev, jsDocOptions, debug } = configuration;
const { entryFiles, productionFiles, projectFiles, isIncludeEntryFiles } = configuration;

@@ -23,6 +23,6 @@ const updateMessage = (0, progress_1.getMessageUpdater)(configuration);

const [usedEntryFiles, usedNonEntryFiles] = (0, project_1.partitionSourceFiles)(usedProductionFiles, entryFiles);
(0, debug_1.debugLogSourceFiles)(configuration, 1, 'Used production files', usedProductionFiles);
(0, debug_1.debugLogSourceFiles)(configuration, 1, 'Unreferenced production files', unreferencedProductionFiles);
(0, debug_1.debugLogSourceFiles)(configuration, 1, 'Used entry files', usedEntryFiles);
(0, debug_1.debugLogSourceFiles)(configuration, 1, 'Used non-entry files', usedNonEntryFiles);
(0, debug_1.debugLogSourceFiles)(debug, 1, 'Used production files', usedProductionFiles);
(0, debug_1.debugLogSourceFiles)(debug, 1, 'Unreferenced production files', unreferencedProductionFiles);
(0, debug_1.debugLogSourceFiles)(debug, 1, 'Used entry files', usedEntryFiles);
(0, debug_1.debugLogSourceFiles)(debug, 1, 'Used non-entry files', usedNonEntryFiles);
const issues = {

@@ -55,3 +55,3 @@ files: new Set(unreferencedProductionFiles.map(file => file.getFilePath())),

const { filePath, symbol } = issue;
const key = node_path_1.default.relative(workingDir, filePath);
const key = node_path_1.default.relative(workingDir, filePath).replace(/\\/g, '/');
issues[issueType][key] = issues[issueType][key] ?? {};

@@ -58,0 +58,0 @@ issues[issueType][key][symbol] = issue;

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

isDev: boolean;
tsConfigPaths: string[];
tsConfigPathGlobs: string[];
isShowProgress: boolean;

@@ -71,0 +71,0 @@ jsDocOptions: {

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

import type { ImportedConfiguration, LocalConfiguration } from '../types';
import type { ImportedConfiguration, LocalConfiguration, Report } from '../types';
export declare const resolveConfig: (importedConfiguration: ImportedConfiguration, options?: {

@@ -6,2 +6,2 @@ workingDir?: string;

}) => LocalConfiguration | undefined;
export declare const resolveIncludedIssueTypes: (includeArg: string[], excludeArg: string[], resolvedConfig?: LocalConfiguration) => import("../types").Report;
export declare const resolveIncludedIssueTypes: (includeArg: string[], excludeArg: string[], resolvedConfig?: LocalConfiguration) => Report;

@@ -34,13 +34,11 @@ "use strict";

const groups = ['files', ...deps, 'unlisted', 'exports', 'types', 'nsExports', 'nsTypes', 'duplicates'];
const include = [includeArg, resolvedConfig?.include ?? []]
.flat()
.map(value => value.split(','))
.flat();
const exclude = [excludeArg, resolvedConfig?.exclude ?? []]
.flat()
.map(value => value.split(','))
.flat();
const includes = (include.length > 0 ? include : groups).filter(group => !exclude.includes(group));
return groups.reduce((r, group) => ((r[group] = includes.includes(group)), r), {});
const normalizedIncludesArg = includeArg.map(value => value.split(',')).flat();
const normalizedExcludesArg = excludeArg.map(value => value.split(',')).flat();
const excludes = (resolvedConfig?.exclude ?? []).filter(exclude => !normalizedIncludesArg.includes(exclude));
const includes = (resolvedConfig?.include ?? []).filter(include => !normalizedExcludesArg.includes(include));
const include = [normalizedIncludesArg, includes].flat();
const exclude = [normalizedExcludesArg, excludes].flat();
const included = (include.length > 0 ? include : groups).filter(group => !exclude.includes(group));
return groups.reduce((types, group) => ((types[group] = included.includes(group)), types), {});
};
exports.resolveIncludedIssueTypes = resolveIncludedIssueTypes;
import type { SourceFile } from 'ts-morph';
declare type Config = {
debug: {
isEnabled: boolean;
level: number;
};
declare type Debug = {
isEnabled: boolean;
level: number;
};
export declare const debugLogObject: (config: Config, minimumLevel: number, name: string, obj: unknown) => void;
export declare const debugLogFiles: (config: Config, minimumLevel: number, name: string, filePaths: string[]) => void;
export declare const debugLogSourceFiles: (config: Config, minimumLevel: number, name: string, sourceFiles: SourceFile[]) => void;
export declare const debugLogObject: (debug: Debug, minimumLevel: number, name: string, obj: unknown) => void;
export declare const debugLogFiles: (debug: Debug, minimumLevel: number, name: string, filePaths: string[]) => void;
export declare const debugLogSourceFiles: (debug: Debug, minimumLevel: number, name: string, sourceFiles: SourceFile[]) => void;
export {};

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

const logArray = (collection) => console.log(node_util_1.default.inspect(collection, { maxArrayLength: null }));
const debugLogObject = (config, minimumLevel, name, obj) => {
if (minimumLevel > config.debug.level)
const debugLogObject = (debug, minimumLevel, name, obj) => {
if (minimumLevel > debug.level)
return;

@@ -17,6 +17,5 @@ console.log(`[knip] ${name}:`);

exports.debugLogObject = debugLogObject;
const debugLogFiles = (config, minimumLevel, name, filePaths) => {
if (minimumLevel > config.debug.level)
const debugLogFiles = (debug, minimumLevel, name, filePaths) => {
if (minimumLevel > debug.level)
return;
const { debug } = config;
if (debug.level > 1) {

@@ -31,6 +30,5 @@ console.debug(`[knip] ${name} (${filePaths.length}):`);

exports.debugLogFiles = debugLogFiles;
const debugLogSourceFiles = (config, minimumLevel, name, sourceFiles) => {
if (minimumLevel > config.debug.level)
const debugLogSourceFiles = (debug, minimumLevel, name, sourceFiles) => {
if (minimumLevel > debug.level)
return;
const { debug } = config;
if (debug.level > 1) {

@@ -45,4 +43,4 @@ console.debug(`[knip] ${name} (${sourceFiles.length}):`);

exports.debugLogSourceFiles = debugLogSourceFiles;
const debugLogDiff = (config, minimumLevel, name, arrA, arrB) => {
if (minimumLevel > config.debug.level)
const debugLogDiff = (debug, minimumLevel, name, arrA, arrB) => {
if (minimumLevel > debug.level)
return;

@@ -49,0 +47,0 @@ const onlyInA = arrA.filter(itemA => !arrB.includes(itemA)).sort();

@@ -10,5 +10,20 @@ "use strict";

const micromatch_1 = __importDefault(require("micromatch"));
const ts_morph_helpers_1 = require("ts-morph-helpers");
const compact = (collection) => Array.from(new Set(collection)).filter((value) => Boolean(value));
const findRequireModuleSpecifiers = (sourceFile) => (0, ts_morph_helpers_1.findCallExpressionsByName)(sourceFile, 'require').map(expression => expression.getFirstDescendantByKind(ts_morph_1.ts.SyntaxKind.StringLiteral));
const isExternalDependency = (moduleSpecifier, tsConfigPathGlobs) => {
if (moduleSpecifier.startsWith('.'))
return false;
if ((0, is_builtin_module_1.default)(moduleSpecifier))
return false;
if (tsConfigPathGlobs.length > 0 && micromatch_1.default.isMatch(moduleSpecifier, tsConfigPathGlobs))
return false;
return true;
};
const resolvePackageName = (moduleSpecifier) => {
const parts = moduleSpecifier.split('/').slice(0, 2);
return moduleSpecifier.startsWith('@') ? parts.join('/') : parts[0];
};
const getDependencyAnalyzer = (configuration) => {
const { dependencies, devDependencies, peerDependencies, optionalDependencies, tsConfigPaths } = configuration;
const { dependencies, devDependencies, peerDependencies, optionalDependencies, tsConfigPathGlobs } = configuration;
const productionDependencies = [...dependencies, ...peerDependencies, ...optionalDependencies];

@@ -19,17 +34,8 @@ const referencedDependencies = new Set();

const importLiterals = sourceFile.getImportStringLiterals();
const requires = sourceFile
.getDescendantsOfKind(ts_morph_1.ts.SyntaxKind.CallExpression)
.filter(callExpression => callExpression.getExpression().getText() === 'require')
.map(expression => expression.getFirstDescendantByKind(ts_morph_1.ts.SyntaxKind.StringLiteral));
const literals = compact([importLiterals, requires].flat());
literals.forEach(importLiteral => {
const moduleSpecifier = importLiteral.getLiteralText();
if (moduleSpecifier.startsWith('.'))
const requireCallExpressions = findRequireModuleSpecifiers(sourceFile);
const moduleSpecifiers = compact([importLiterals, requireCallExpressions].flat()).map(i => i.getLiteralText());
moduleSpecifiers.forEach(moduleSpecifier => {
if (!isExternalDependency(moduleSpecifier, tsConfigPathGlobs))
return;
if ((0, is_builtin_module_1.default)(moduleSpecifier))
return;
if (tsConfigPaths.length > 0 && micromatch_1.default.isMatch(moduleSpecifier, tsConfigPaths))
return;
const parts = moduleSpecifier.split('/').slice(0, 2);
const packageName = moduleSpecifier.startsWith('@') ? parts.join('/') : parts[0];
const packageName = resolvePackageName(moduleSpecifier);
if (!productionDependencies.includes(packageName) && !devDependencies.includes(packageName)) {

@@ -36,0 +42,0 @@ unresolvedDependencies.add({ filePath: sourceFile.getFilePath(), symbol: moduleSpecifier });

export declare const relative: (to: string) => string;
export declare const resolvePaths: ({ workingDir, patterns, ignore, gitignore, }: {
export declare const resolvePaths: ({ cwd, workingDir, patterns, ignore, gitignore, }: {
cwd: string;
workingDir: string;

@@ -4,0 +5,0 @@ patterns: string[];

@@ -21,8 +21,8 @@ "use strict";

if (pattern.startsWith('!'))
return '!' + node_path_1.default.join(workingDir, pattern.slice(1));
return node_path_1.default.join(workingDir, pattern);
return '!' + node_path_1.default.posix.join(workingDir, pattern.slice(1));
return node_path_1.default.posix.join(workingDir, pattern);
};
const resolvePaths = async ({ workingDir, patterns, ignore, gitignore, }) => glob(patterns.map(pattern => prependDirToPattern((0, exports.relative)(workingDir), pattern)), {
const resolvePaths = async ({ cwd, workingDir, patterns, ignore, gitignore, }) => glob(patterns.map(pattern => prependDirToPattern(node_path_1.default.posix.relative(cwd, workingDir), pattern)), {
cwd,
ignore,
ignore: [...ignore, '**/node_modules'],
gitignore,

@@ -29,0 +29,0 @@ absolute: true,

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

@@ -28,3 +28,3 @@ "keywords": [

"knip": "node ./dist/cli.js --jsdoc public",
"test": "node --loader tsx --test test/*.spec.ts",
"test": "globstar -- node --loader tsx --test \"test/*.spec.ts\"",
"watch": "tsc --watch",

@@ -54,2 +54,3 @@ "build": "rm -rf dist && tsc",

"@types/node": "18.11.2",
"globstar": "1.0.0",
"prettier": "2.7.1",

@@ -56,0 +57,0 @@ "release-it": "15.5.0",

@@ -128,6 +128,8 @@ # ✂️ Knip

1. This may also include dependencies that could not be resolved properly (such as non-relative `local/dir/file.ts` not
and `local` not being in `node_modules`).
1. This includes dependencies that could not be resolved. For instance, what does `unresolved/dir/module` mean?
- To target something in the (missing) `node_modules/unresolved` package?
- Target a local module that should have a relative path?
- It does not match any `paths` entry in `tsconfig.json#compilerOptions`.
2. The variable or type is not referenced directly, and has become a member of a namespace. That's why Knip is not sure
whether this export can be removed, so please look into it:
whether this export can be removed, so please look into it.

@@ -144,4 +146,4 @@ You can `--include` or `--exclude` any of the types to slice & dice the report to your needs. Alternatively, they can be

- Unlisted dependencies should be added to `package.json`.
- Unused exports and types: remove the `export` keyword in front of unused exports. Then you (or tools such as the
TypeScript language server in VS Code and/or ESLint) can see whether the variable or type is used within the same
- Unused exports and types: remove the `export` keyword in front of unused exports. Then you (or tools such as
TypeScript language services in VS Code and/or ESLint) can see whether the variable or type is used within the same
file. If this is not the case, it can be removed.

@@ -148,0 +150,0 @@

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