Closed gajus closed 1 year ago
https://github.com/webpro/knip/blob/main/docs/handling-issues.md#unlisted-dependencies
Suggests to add the dependency directly, but in this case it would be undesirable.
I ended up adding .eslintrc.js
to ignore
and that got rid of the "Unlisted dependencies" error, but introduced:
Unused devDependencies (2)
@contra/eslint-config package.json
eslint-plugin-relay package.json
At least I can ignore these, but wish these was a better solution.
Knip doesn't use rules
to infer dependencies (it looks at extends
plugins
, parser
and settings
, and also in overrides
for the same).
But looking at https://github.com/gajus/eslint-config-canonical/blob/main/configurations/eslintrc.js, this looks like a bug in Knip. Will look into it soon!
Not sure if I'm trying the right thing, but in that repo, either one of these configs (knip.json
) seem to work well:
{
"entry": ["compare/*.js"],
"eslint": ["*.js"]
}
{
"workspaces": {
".": {
"eslint": ["*.js"]
},
"compare": {}
}
}
The error is in a repository that consumes eslint-config-canonical
as a dependency.
Then I wonder why it is reported as unlisted. Got a rep(r)o? :)
If helpful, this is the entire config:
const path = require('node:path');
const ruleOverrides = {
'@typescript-eslint/dot-notation': 0,
'@typescript-eslint/naming-convention': 0,
'@typescript-eslint/no-misused-promises': 0,
'@typescript-eslint/no-redeclare': 0,
'@typescript-eslint/no-unnecessary-condition': 0,
'@typescript-eslint/prefer-nullish-coalescing': 0,
'@typescript-eslint/prefer-optional-chain': 0,
'@typescript-eslint/prefer-string-starts-ends-with': 0,
'@typescript-eslint/promise-function-async': 0,
'canonical/filename-match-regex': 0,
'canonical/id-match': 0,
'canonical/prefer-import-alias': [
2,
{
aliases: [
{
alias: '@/',
matchParent: path.resolve(__dirname, 'src/components'),
matchPath: '^src\\/',
},
{
alias: '@/',
matchParent: path.resolve(__dirname, 'src'),
matchPath: '^src\\/',
},
{
alias: '@/',
matchPath: '^src\\/',
maxRelativeDepth: 2,
},
],
},
],
'canonical/prefer-use-mount': 2,
'default-case': 0,
'default-case-last': 0,
'import/extensions': 0,
'import/no-named-default': 0,
'jsx-a11y/mouse-events-have-key-events': 0,
'no-restricted-globals': [
2,
{
message: 'Please use localStorage from @/services/storage',
name: 'localStorage',
},
{
message: 'Please use sessionStorage from @/services/storage',
name: 'sessionStorage',
},
],
'no-restricted-imports': [
2,
{
paths: [
{
importNames: ['default'],
message: 'Please import a named export.',
name: 'react',
},
{
importNames: ['graphql'],
message: 'Please use graphql from relay-runtime.',
name: 'react-relay',
},
{
importNames: ['useFlags'],
message: 'Please use useFeaturedFlags from @/hooks.',
name: 'launchdarkly-react-client-sdk',
},
{
importNames: ['motion', 'm'],
message: 'Please use animated from @/components/Primitives',
name: 'framer-motion',
},
{
importNames: ['useMutation'],
message: 'Please use useContraMutation from @/hooks.',
name: 'react-relay',
},
{
message: 'Please use zod for validation.',
name: 'yup',
},
{
importNames: ['captureException'],
message: 'Please use captureException from @/utilities.',
name: '@sentry/react',
},
{
importNames: ['default'],
message: 'Please use dayjs from @/utilities/dayjs.',
name: 'dayjs',
},
],
},
],
'no-restricted-properties': [
2,
{
message: 'Please use localStorage from @/services/storage',
object: 'window',
property: 'localStorage',
},
{
message: 'Please use sessionStorage from @/services/storage',
object: 'window',
property: 'sessionStorage',
},
{
message: 'Use fast-safe-stringify',
object: 'JSON',
property: 'stringify',
},
// TODO [2023-04-23]: needs to be re-enabled
// {
// message: 'Please use the new color system accessed via theme.colorsV2',
// object: 'theme',
// property: 'colors',
// },
],
'no-restricted-syntax': ['error', 'Literal[value=/^@validate$/i]'],
'no-warning-comments': 0,
'node/no-process-env': 0,
'node/no-sync': 0,
'prefer-destructuring': 0,
'prefer-object-spread': 0,
'promise/prefer-await-to-then': 0,
'react/forbid-component-props': 0,
'react/hook-use-state': 0,
'react/iframe-missing-sandbox': 0,
'react/no-array-index-key': 0,
'react/no-invalid-html-attribute': 0,
'react/no-unstable-nested-components': 0,
'react/no-unused-prop-types': 0,
'react/prop-types': 0,
'regexp/no-obscure-range': 0,
'regexp/no-super-linear-backtracking': 0,
'regexp/no-unused-capturing-group': 0,
'relay/compat-uses-vars': 2,
'relay/function-required-argument': 2,
'relay/graphql-naming': 2,
'relay/graphql-syntax': 2,
'relay/hook-required-argument': 2,
'relay/must-colocate-fragment-spreads': 0,
'relay/no-future-added-value': 0,
'relay/unused-fields': 2,
'require-unicode-regexp': 0,
'unicorn/import-index': 0,
'unicorn/no-array-callback-reference': 0,
'unicorn/no-array-for-each': 0,
'unicorn/no-array-reduce': 0,
'unicorn/no-thenable': 0,
'unicorn/no-unsafe-regex': 0,
'unicorn/no-useless-switch-case': 0,
'unicorn/prefer-code-point': 0,
'unicorn/prefer-node-protocol': 0,
'vitest/max-nested-describe': 0,
};
module.exports = {
extends: ['@contra/eslint-config'],
ignorePatterns: ['/dist', '**/__generated__'],
overrides: [
{
files: '*.{ts,tsx}',
rules: {
'canonical/no-barrel-import': 2,
...ruleOverrides,
},
settings: {
'import/parsers': {
'@typescript-eslint/parser': ['.ts', '.tsx'],
},
'import/resolver': {
typescript: {
project: path.resolve(__dirname, 'tsconfig.json'),
},
},
},
},
{
extends: ['canonical/jsx-a11y'],
files: '*.tsx',
rules: ruleOverrides,
},
{
files: './tests/**/*',
rules: {
'import/no-cycle': 0,
},
},
],
plugins: ['relay'],
root: true,
};
Given only that config, Knip finds these dependencies (rules
are ignored):
'@contra/eslint-config',
'eslint-plugin-relay',
'@typescript-eslint/parser',
'eslint-import-resolver-typescript',
'eslint-config-canonical',
Normally the dependencies would be direct or peer dependencies. Not sure how Knip could make an exception here without affecting other setups.
Going to close this issue. Since the dependencies are "directly" referenced in the ESLint config, Knip can only assume they should be listed in package.json
.
Fixing this issue would mean transitive dependencies are OK to directly depend on, and I prefer not to go that way.
That's fair.
I would still consider ignoreUnlisted
configuration for cases where it is desirable to support transitive dependencies.
I am trying to restructure out project to avoid this altogether.
@webpro I have a same issue.
I'm sharing config files across multiple repos as an npm library, which itself contains eslint configuration and dependencies.
Typically eslint config of an app consuming this library looks like this:
const merge = require("deepmerge");
const { createEslintConfig } = require("@omnic/widget-config");
const rootConfig = createEslintConfig();
module.exports = merge(rootConfig, {
root: true,
env: { node: true },
rules: { "@typescript-eslint/no-var-requires": "off" },
});
And here is my output
Fixing this issue would mean transitive dependencies are OK to directly depend on, and I prefer not to go that way.
Isn't it possible to optionally enable transitive dependencies check? It would be very helpful in case of using Knip for module federations for example, cause they usually have some shared configs/ui-kits, and duplicating all the dependencies is not a way
P.S. Also can't figure out how to deal with these .svg
imports (I'm using vite
+ svgr
plugin)
Isn't it possible to optionally enable transitive dependencies check?
This is a common yet non-trivial issue to solve. It's on my radar.
P.S. Also can't figure out how to deal with these .svg imports (I'm using vite + svgr plugin)
This is usually worked around by using relative imports (e.g. ./src/icon.svg
).
It looks like it infers that the plugin is in use based on which rules are mentioned:
This is smart. However, in cases like eslint-config-canonical, the config itself has a dependency on the plugins, and this becomes and undesirable.