Comparing version 0.7.3 to 0.8.0
@@ -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 |
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
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
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
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
75803
39
1158
456