diff --git a/packages/knip/fixtures/plugins/xo/.xo-config.js b/packages/knip/fixtures/plugins/xo/.xo-config.js new file mode 100644 index 000000000..836dbfa6d --- /dev/null +++ b/packages/knip/fixtures/plugins/xo/.xo-config.js @@ -0,0 +1,9 @@ +module.exports = { + space: true, + prettier: true, + plugins: ['unused-imports'], + parserOptions: {emitDecoratorMetadata: true}, + rules: { + 'func-names': ['error', 'always'], + }, +}; diff --git a/packages/knip/fixtures/plugins/xo/node_modules/xo/index.js b/packages/knip/fixtures/plugins/xo/node_modules/xo/index.js new file mode 100644 index 000000000..e69de29bb diff --git a/packages/knip/fixtures/plugins/xo/node_modules/xo/package.json b/packages/knip/fixtures/plugins/xo/node_modules/xo/package.json new file mode 100644 index 000000000..0510f1374 --- /dev/null +++ b/packages/knip/fixtures/plugins/xo/node_modules/xo/package.json @@ -0,0 +1,4 @@ +{ + "name": "xo", + "bin": "index.js" +} diff --git a/packages/knip/fixtures/plugins/xo/package.json b/packages/knip/fixtures/plugins/xo/package.json new file mode 100644 index 000000000..1eed5e227 --- /dev/null +++ b/packages/knip/fixtures/plugins/xo/package.json @@ -0,0 +1,13 @@ +{ + "name": "@fixtures/xo", + "scripts": { + "test": "xo" + }, + "devDependencies": { + "xo": "^0.24.0" + }, + "xo": { + "space": true, + "plugins": ["eslint-comments"] + } +} diff --git a/packages/knip/fixtures/plugins/xo/xo-config.cjs b/packages/knip/fixtures/plugins/xo/xo-config.cjs new file mode 100644 index 000000000..2780f4402 --- /dev/null +++ b/packages/knip/fixtures/plugins/xo/xo-config.cjs @@ -0,0 +1,8 @@ +const mySharedConfig = require('glob'); + +module.exports = { + ...mySharedConfig, + rules: { + 'func-names': 'off', + }, +}; \ No newline at end of file diff --git a/packages/knip/schema.json b/packages/knip/schema.json index f732b9a8a..e57fee421 100644 --- a/packages/knip/schema.json +++ b/packages/knip/schema.json @@ -507,6 +507,10 @@ "title": "wrangler plugin configuration (https://github.com/webpro/knip/blob/main/src/plugins/wrangler/README.md)", "$ref": "#/definitions/plugin" }, + "xo": { + "title": "xo plugin configuration (https://github.com/webpro/knip/blob/main/src/plugins/xo/README.md)", + "$ref": "#/definitions/plugin" + }, "yorkie": { "title": "yorkie plugin configuration (https://github.com/webpro/knip/blob/main/src/plugins/yorkie/README.md)", "$ref": "#/definitions/plugin" diff --git a/packages/knip/src/ConfigurationValidator.ts b/packages/knip/src/ConfigurationValidator.ts index 1c201e237..a81858b7e 100644 --- a/packages/knip/src/ConfigurationValidator.ts +++ b/packages/knip/src/ConfigurationValidator.ts @@ -135,6 +135,7 @@ const pluginsSchema = z.object({ webpack: pluginSchema, wireit: pluginSchema, wrangler: pluginSchema, + xo: pluginSchema, yorkie: pluginSchema, }); diff --git a/packages/knip/src/plugins/index.ts b/packages/knip/src/plugins/index.ts index 09a31f3d5..d8c18f918 100644 --- a/packages/knip/src/plugins/index.ts +++ b/packages/knip/src/plugins/index.ts @@ -58,3 +58,4 @@ export { default as webpack } from './webpack/index.js'; export { default as wireit } from './wireit/index.js'; export { default as wrangler } from './wrangler/index.js'; export { default as yorkie } from './yorkie/index.js'; +export { default as xo } from './xo/index.js'; diff --git a/packages/knip/src/plugins/xo/index.ts b/packages/knip/src/plugins/xo/index.ts new file mode 100644 index 000000000..055f15a3f --- /dev/null +++ b/packages/knip/src/plugins/xo/index.ts @@ -0,0 +1,34 @@ +import type { EnablerPatterns } from '#p/types/config.js'; +import type { IsPluginEnabled, Plugin, ResolveConfig } from '#p/types/plugins.js'; +import { hasDependency } from '#p/util/plugin.js'; +import { getDependenciesDeep } from '../eslint/helpers.js'; +import type { XOConfig } from './types.js'; + +// link to xo docs: https://github.com/xojs/xo#config + +const title = 'xo'; + +const enablers: EnablerPatterns = ['xo']; + +const isEnabled: IsPluginEnabled = ({ dependencies, config }) => + hasDependency(dependencies, enablers) || 'xo' in config; + +const packageJsonPath = 'xo'; +const config: string[] = ['{.,}xo-config.{js,cjs,json,}', 'package.json']; + +const entry: string[] = ['{.,}xo-config.{js,cjs}']; + +const resolveConfig: ResolveConfig = async (config, options) => { + const dependencies = await getDependenciesDeep(config, options); + return [...dependencies]; +}; + +export default { + title, + enablers, + isEnabled, + packageJsonPath, + entry, + config, + resolveConfig, +} satisfies Plugin; diff --git a/packages/knip/src/plugins/xo/types.ts b/packages/knip/src/plugins/xo/types.ts new file mode 100644 index 000000000..8b89ac814 --- /dev/null +++ b/packages/knip/src/plugins/xo/types.ts @@ -0,0 +1,13 @@ +import type { ESLintConfig } from '../eslint/types.js'; + +export type XOConfig = ESLintConfig & { + envs?: string[] | undefined; + globals?: string[] | undefined; + ignores?: string[] | undefined; + nodeVersion?: string | boolean | undefined; + prettier?: boolean | undefined; + printConfig?: string | undefined; + semicolon?: boolean | undefined; + space?: boolean | number | undefined; + webpack?: boolean | object | undefined; +}; diff --git a/packages/knip/test/plugins/xo.test.ts b/packages/knip/test/plugins/xo.test.ts new file mode 100644 index 000000000..7774edbea --- /dev/null +++ b/packages/knip/test/plugins/xo.test.ts @@ -0,0 +1,26 @@ +import { test } from 'bun:test'; +import assert from 'node:assert/strict'; +import { main } from '../../src/index.js'; +import { resolve } from '../../src/util/path.js'; +import baseArguments from '../helpers/baseArguments.js'; +import baseCounters from '../helpers/baseCounters.js'; + +const cwd = resolve('fixtures/plugins/xo'); + +test('Find dependencies with the xo plugin', async () => { + const { issues, counters } = await main({ + ...baseArguments, + cwd, + }); + + assert(issues.unlisted['.xo-config.js']['eslint-plugin-unused-imports']); + assert(issues.unlisted['xo-config.cjs']['glob']); + assert(issues.unlisted['package.json']['eslint-plugin-eslint-comments']); + + assert.deepEqual(counters, { + ...baseCounters, + processed: 2, + unlisted: 3, + total: 2, + }); +});