Comparing version 0.9.1 to 0.10.0
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 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
78274
1163
513
8