From 9ed019ac3b039ffd2298dfa8675b47e0ad3794b4 Mon Sep 17 00:00:00 2001 From: Jamie Feingold Date: Tue, 22 Sep 2026 14:28:54 -0500 Subject: [PATCH] fix: resolving autofixable eslint violations (W-24099831) --- eslint-suppressions.json | 105 +-------------------------- src/commands/plugins/index.ts | 5 +- src/commands/plugins/inspect.ts | 19 +++-- src/commands/plugins/install.ts | 7 +- src/commands/plugins/link.ts | 2 + src/commands/plugins/reset.ts | 3 +- src/commands/plugins/uninstall.ts | 6 +- src/hooks/update.ts | 2 +- src/log-level.ts | 2 +- src/npm.ts | 10 +-- src/plugins.ts | 24 +++--- src/spawn.ts | 6 +- src/util.ts | 2 +- src/yarn.ts | 10 +-- test/integration/link.integration.ts | 6 +- test/plugins.test.ts | 10 +-- 16 files changed, 70 insertions(+), 149 deletions(-) diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 07222e0e..1c0b38c1 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -5,15 +5,6 @@ } }, "src/commands/plugins/index.ts": { - "@stylistic/lines-between-class-members": { - "count": 1 - }, - "@typescript-eslint/consistent-type-definitions": { - "count": 1 - }, - "@typescript-eslint/consistent-type-imports": { - "count": 1 - }, "@typescript-eslint/no-unnecessary-type-conversion": { "count": 1 }, @@ -37,20 +28,11 @@ "@eslint-community/eslint-comments/require-description": { "count": 1 }, - "@stylistic/lines-between-class-members": { - "count": 2 - }, - "@typescript-eslint/consistent-type-imports": { - "count": 1 - }, - "@typescript-eslint/no-restricted-types": { - "count": 3 - }, "@typescript-eslint/no-unsafe-argument": { "count": 1 }, "@typescript-eslint/no-unsafe-assignment": { - "count": 3 + "count": 4 }, "@typescript-eslint/no-unsafe-call": { "count": 1 @@ -64,9 +46,6 @@ "unicorn/consistent-boolean-name": { "count": 1 }, - "unicorn/consistent-conditional-object-spread": { - "count": 2 - }, "unicorn/import-style": { "count": 1 }, @@ -78,12 +57,6 @@ "@eslint-community/eslint-comments/require-description": { "count": 1 }, - "@stylistic/lines-between-class-members": { - "count": 3 - }, - "@typescript-eslint/consistent-type-imports": { - "count": 1 - }, "@typescript-eslint/no-shadow": { "count": 1 }, @@ -95,26 +68,12 @@ }, "unicorn/no-computed-property-existence-check": { "count": 1 - }, - "unicorn/no-unnecessary-boolean-comparison": { - "count": 1 - } - }, - "src/commands/plugins/link.ts": { - "@stylistic/lines-between-class-members": { - "count": 2 } }, "src/commands/plugins/reset.ts": { "@eslint-community/eslint-comments/require-description": { "count": 1 }, - "@stylistic/lines-between-class-members": { - "count": 1 - }, - "@typescript-eslint/promise-function-async": { - "count": 1 - }, "unicorn/import-style": { "count": 1 }, @@ -125,28 +84,9 @@ "src/commands/plugins/uninstall.ts": { "@eslint-community/eslint-comments/require-description": { "count": 1 - }, - "@stylistic/lines-between-class-members": { - "count": 2 - }, - "@typescript-eslint/no-unnecessary-type-assertion": { - "count": 2 - } - }, - "src/hooks/update.ts": { - "@typescript-eslint/consistent-type-imports": { - "count": 1 - } - }, - "src/log-level.ts": { - "@typescript-eslint/consistent-type-imports": { - "count": 1 } }, "src/npm.ts": { - "@typescript-eslint/consistent-type-imports": { - "count": 3 - }, "@typescript-eslint/default-param-last": { "count": 1 }, @@ -159,9 +99,6 @@ "@typescript-eslint/no-unsafe-member-access": { "count": 1 }, - "@typescript-eslint/prefer-readonly": { - "count": 2 - }, "unicorn/consistent-class-member-order": { "count": 1 }, @@ -170,12 +107,6 @@ } }, "src/plugins.ts": { - "@typescript-eslint/consistent-type-imports": { - "count": 3 - }, - "@typescript-eslint/no-confusing-void-expression": { - "count": 1 - }, "@typescript-eslint/no-dynamic-delete": { "count": 1 }, @@ -195,12 +126,9 @@ "count": 1 }, "unicorn/consistent-boolean-name": { - "count": 2 - }, - "unicorn/consistent-class-member-order": { "count": 1 }, - "unicorn/consistent-conditional-object-spread": { + "unicorn/consistent-class-member-order": { "count": 1 }, "unicorn/import-style": { @@ -211,15 +139,9 @@ }, "unicorn/prefer-array-some": { "count": 1 - }, - "unicorn/prefer-early-return": { - "count": 1 } }, "src/spawn.ts": { - "@typescript-eslint/consistent-type-imports": { - "count": 1 - }, "@typescript-eslint/default-param-last": { "count": 1 }, @@ -228,9 +150,6 @@ }, "n/prefer-global/buffer": { "count": 2 - }, - "unicorn/consistent-boolean-name": { - "count": 1 } }, "src/util.ts": { @@ -243,26 +162,17 @@ "unicorn/no-array-sort": { "count": 1 }, - "unicorn/no-negated-array-predicate": { - "count": 1 - }, "unicorn/no-useless-recursion": { "count": 1 } }, "src/yarn.ts": { - "@typescript-eslint/consistent-type-imports": { - "count": 3 - }, "@typescript-eslint/default-param-last": { "count": 1 }, "@typescript-eslint/no-unused-private-class-members": { "count": 1 }, - "@typescript-eslint/prefer-readonly": { - "count": 2 - }, "unicorn/consistent-class-member-order": { "count": 1 } @@ -281,9 +191,6 @@ } }, "test/integration/link.integration.ts": { - "@typescript-eslint/no-confusing-void-expression": { - "count": 1 - }, "unicorn/import-style": { "count": 1 } @@ -303,12 +210,6 @@ } }, "test/plugins.test.ts": { - "@typescript-eslint/array-type": { - "count": 2 - }, - "@typescript-eslint/consistent-type-imports": { - "count": 2 - }, "@typescript-eslint/prefer-nullish-coalescing": { "count": 1 }, @@ -316,4 +217,4 @@ "count": 1 } } -} \ No newline at end of file +} diff --git a/src/commands/plugins/index.ts b/src/commands/plugins/index.ts index 27e191e6..b4ba5159 100644 --- a/src/commands/plugins/index.ts +++ b/src/commands/plugins/index.ts @@ -1,4 +1,4 @@ -import {Command, Flags, Interfaces, Plugin} from '@oclif/core' +import {Command, Flags, type Interfaces, type Plugin} from '@oclif/core' import {dim} from 'ansis' // @ts-expect-error because object-treeify does not have types: https://github.com/blackflux/object-treeify/issues/1077 import treeify from 'object-treeify' @@ -8,7 +8,7 @@ import {sortBy} from '../../util.js' type JitPlugin = {name: string; type: string; version: string} type PluginsJson = Array -interface RecursiveTree { +type RecursiveTree = { [key: string]: RecursiveTree | string } @@ -19,6 +19,7 @@ export default class PluginsIndex extends Command { static flags = { core: Flags.boolean({description: 'Show core plugins.'}), } + plugins!: Plugins public async run(): Promise { diff --git a/src/commands/plugins/inspect.ts b/src/commands/plugins/inspect.ts index 14ddeac9..205bfd85 100644 --- a/src/commands/plugins/inspect.ts +++ b/src/commands/plugins/inspect.ts @@ -1,4 +1,4 @@ -import {Args, Command, Flags, Plugin} from '@oclif/core' +import {Args, Command, Flags, type Plugin} from '@oclif/core' import {bold, dim} from 'ansis' import {readFile} from 'node:fs/promises' import {dirname, join, sep} from 'node:path' @@ -46,6 +46,7 @@ export default class PluginsInspect extends Command { required: true, }), } + static description = 'Displays installation properties of a plugin.' static enableJsonFlag = true static examples = ['<%= config.bin %> <%= command.id %> <%- config.pjson.oclif.examplePlugin || "myplugin" %> '] @@ -53,11 +54,15 @@ export default class PluginsInspect extends Command { help: Flags.help({char: 'h'}), verbose: Flags.boolean({char: 'v'}), } + static strict = false static usage = 'plugins:inspect PLUGIN...' plugins!: Plugins - async findDep(plugin: Plugin, dependency: string): Promise<{pkgPath: null | string; version: null | string}> { + async findDep( + plugin: Plugin, + dependency: string, + ): Promise<{pkgPath: string | undefined; version: string | undefined}> { const dependencyPath = join(...dependency.split('/')) let start = join(plugin.root, 'node_modules') const paths = [start] @@ -80,7 +85,7 @@ export default class PluginsInspect extends Command { } } - return {pkgPath: null, version: null} + return {pkgPath: undefined, version: undefined} } findPlugin(pluginName: string): Plugin { @@ -98,7 +103,7 @@ export default class PluginsInspect extends Command { async inspect(pluginName: string, verbose = false): Promise { const plugin = this.findPlugin(pluginName) - const dependencies: Record = {} + const dependencies: Record = {} const depsJson: Dependencies = {} for (const dep of sortBy(Object.keys({...plugin.pjson.dependencies}), (d) => d)) { // eslint-disable-next-line no-await-in-loop @@ -109,15 +114,15 @@ export default class PluginsInspect extends Command { const versionMsg = dim(from ? `${from} => ${version}` : version) const msg = verbose ? `${dep} ${versionMsg} ${pkgPath}` : `${dep} ${versionMsg}` - dependencies[msg] = null + dependencies[msg] = undefined depsJson[dep] = {from, version} } const tree = { [bold.cyan(plugin.name)]: { [`version ${plugin.version}`]: null, - ...(plugin.tag ? {[`tag ${plugin.tag}`]: null} : {}), - ...(plugin.pjson.homepage ? {[`homepage ${plugin.pjson.homepage}`]: null} : {}), + ...(plugin.tag && {[`tag ${plugin.tag}`]: null}), + ...(plugin.pjson.homepage && {[`homepage ${plugin.pjson.homepage}`]: null}), [`location ${plugin.root}`]: null, commands: Object.fromEntries(sortBy(plugin.commandIDs, (c) => c).map((id) => [id, null])), dependencies, diff --git a/src/commands/plugins/install.ts b/src/commands/plugins/install.ts index d17daf5c..7990a611 100644 --- a/src/commands/plugins/install.ts +++ b/src/commands/plugins/install.ts @@ -1,5 +1,5 @@ /* eslint-disable no-await-in-loop */ -import {Args, Command, Errors, Flags, Interfaces, Plugin, ux} from '@oclif/core' +import {Args, Command, Errors, Flags, type Interfaces, Plugin, ux} from '@oclif/core' import {bold, cyan} from 'ansis' import validate from 'validate-npm-package-name' @@ -11,6 +11,7 @@ export default class PluginsInstall extends Command { static args = { plugin: Args.string({description: 'Plugin to install.', required: true}), } + static description = `Uses npm to install plugins. Installation of a user-installed plugin will override a core plugin. @@ -32,6 +33,7 @@ Use the <%= config.scopedEnvVarKey('NPM_REGISTRY') %> environment variable to se description: 'Install a plugin from a github slug.', }, ] + static flags = { force: Flags.boolean({ char: 'f', @@ -41,7 +43,7 @@ Use the <%= config.scopedEnvVarKey('NPM_REGISTRY') %> environment variable to se jit: Flags.boolean({ hidden: true, async parse(input, ctx) { - if (input === false || input === undefined) return input + if (!input || input === undefined) return input const requestedPlugins = ctx.argv.filter((a) => !a.startsWith('-')) if (requestedPlugins.length === 0) return input @@ -77,6 +79,7 @@ Use the <%= config.scopedEnvVarKey('NPM_REGISTRY') %> environment variable to se exclusive: ['silent'], }), } + static strict = false static summary = 'Installs a plugin into <%= config.bin %>.' flags!: Interfaces.InferredFlags diff --git a/src/commands/plugins/link.ts b/src/commands/plugins/link.ts index 34ce3f3b..ede0a453 100644 --- a/src/commands/plugins/link.ts +++ b/src/commands/plugins/link.ts @@ -8,6 +8,7 @@ export default class PluginsLink extends Command { static args = { path: Args.string({default: '.', description: 'path to plugin', name: 'path', required: true}), } + static description = `Installation of a linked plugin will override a user-installed or core plugin. e.g. If you have a user-installed or core plugin that has a 'hello' command, installing a linked plugin with a 'hello' command will override the user-installed or core plugin implementation. This is useful for development work. @@ -22,6 +23,7 @@ e.g. If you have a user-installed or core plugin that has a 'hello' command, ins }), verbose: Flags.boolean({char: 'v'}), } + static summary = 'Links a plugin into the CLI for development.' async run(): Promise { diff --git a/src/commands/plugins/reset.ts b/src/commands/plugins/reset.ts index ad72c8c7..3466f055 100644 --- a/src/commands/plugins/reset.ts +++ b/src/commands/plugins/reset.ts @@ -15,6 +15,7 @@ export default class Reset extends Command { summary: 'Reinstall all plugins after uninstalling.', }), } + static summary = 'Remove all user-installed and linked plugins.' // eslint-disable-next-line complexity @@ -45,7 +46,7 @@ export default class Reset extends Command { this.log(`- ${file}`) } - await Promise.all(filesToDelete.map((file) => rm(file, {force: true, recursive: true}))) + await Promise.all(filesToDelete.map(async (file) => rm(file, {force: true, recursive: true}))) for (const plugin of userPlugins) { this.log(`✅ ${plugin.type === 'link' ? 'Unlinked' : 'Uninstalled'} ${plugin.name}`) } diff --git a/src/commands/plugins/uninstall.ts b/src/commands/plugins/uninstall.ts index 74c6d7f0..c4236f0a 100644 --- a/src/commands/plugins/uninstall.ts +++ b/src/commands/plugins/uninstall.ts @@ -25,12 +25,14 @@ export default class PluginsUninstall extends Command { static args = { plugin: Args.string({description: 'plugin to uninstall'}), } + static description = 'Removes a plugin from the CLI.' static examples = ['<%= config.bin %> <%= command.id %> <%- config.pjson.oclif.examplePlugin || "myplugin" %>'] static flags = { help: Flags.help({char: 'h'}), verbose: Flags.boolean({char: 'v'}), } + static strict = false // In this case we want these operations to happen @@ -52,9 +54,7 @@ export default class PluginsUninstall extends Command { if (!unfriendly) { const p = this.config.getPluginsList().find((p) => p.name === plugin) if (p?.parent) - return this.error( - `${friendly} is installed via plugin ${p.parent!.name}, uninstall ${p.parent!.name} instead`, - ) + return this.error(`${friendly} is installed via plugin ${p.parent.name}, uninstall ${p.parent.name} instead`) return this.error(`${friendly} is not installed`) } diff --git a/src/hooks/update.ts b/src/hooks/update.ts index e245e722..786868d7 100644 --- a/src/hooks/update.ts +++ b/src/hooks/update.ts @@ -1,4 +1,4 @@ -import {Hook} from '@oclif/core' +import {type Hook} from '@oclif/core' import Plugins from '../plugins.js' diff --git a/src/log-level.ts b/src/log-level.ts index 7d7989b4..3caf283d 100644 --- a/src/log-level.ts +++ b/src/log-level.ts @@ -1,4 +1,4 @@ -import {Config} from '@oclif/core' +import {type Config} from '@oclif/core' const LOG_LEVELS = ['silent', 'error', 'warn', 'notice', 'http', 'info', 'verbose', 'silly'] as const export type LogLevel = (typeof LOG_LEVELS)[number] diff --git a/src/npm.ts b/src/npm.ts index 99713969..f32fd46c 100644 --- a/src/npm.ts +++ b/src/npm.ts @@ -1,11 +1,11 @@ -import {Errors, Interfaces, ux} from '@oclif/core' +import {Errors, type Interfaces, ux} from '@oclif/core' import makeDebug from 'debug' import {readFile} from 'node:fs/promises' import {createRequire} from 'node:module' import {join, sep} from 'node:path' -import {LogLevel} from './log-level.js' -import {ExecOptions, Output, spawn} from './spawn.js' +import {type LogLevel} from './log-level.js' +import {type ExecOptions, type Output, spawn} from './spawn.js' const debug = makeDebug('@oclif/plugin-plugins:npm') @@ -15,8 +15,8 @@ type InstallOptions = ExecOptions & { export class NPM { private bin: string | undefined - private config: Interfaces.Config - private logLevel: LogLevel + private readonly config: Interfaces.Config + private readonly logLevel: LogLevel public constructor({config, logLevel}: {config: Interfaces.Config; logLevel: LogLevel}) { this.config = config diff --git a/src/plugins.ts b/src/plugins.ts index 2304c08f..2b09db4a 100644 --- a/src/plugins.ts +++ b/src/plugins.ts @@ -1,4 +1,4 @@ -import {Config, Errors, Interfaces, ux} from '@oclif/core' +import {Config, Errors, type Interfaces, ux} from '@oclif/core' import {bold} from 'ansis' import makeDebug from 'debug' import {spawn} from 'node:child_process' @@ -7,9 +7,9 @@ import {basename, dirname, join, parse, resolve} from 'node:path' import {fileURLToPath} from 'node:url' import {gt, valid, validRange} from 'semver' -import {LogLevel} from './log-level.js' +import {type LogLevel} from './log-level.js' import {NPM} from './npm.js' -import {Output} from './spawn.js' +import {type Output} from './spawn.js' import {uniqWith} from './util.js' import {Yarn} from './yarn.js' @@ -66,8 +66,8 @@ function extractIssuesLocation( } function notifyUser(plugin: Config, output: Output): void { - const containsWarnings = [...output.stdout, ...output.stderr].some((l) => l.includes('npm WARN')) - if (containsWarnings) { + const isContainsWarnings = [...output.stdout, ...output.stderr].some((l) => l.includes('npm WARN')) + if (isContainsWarnings) { ux.stderr(bold.yellow(`\nThese warnings can only be addressed by the owner(s) of ${plugin.name}.`)) if (plugin.pjson.bugs || plugin.pjson.repository) { @@ -246,7 +246,9 @@ export default class Plugins { return plugin } catch (error: unknown) { this.debug('error installing plugin:', error) - await this.uninstall(name).catch((error) => this.debug(error)) + await this.uninstall(name).catch((error) => { + this.debug(error) + }) if (String(error).includes('EACCES')) { throw new Errors.CLIError(error as Error, { @@ -408,10 +410,12 @@ export default class Plugins { } private async ensurePJSON() { - if (!(await fileExists(this.pjsonPath))) { - this.debug(`creating ${this.pjsonPath} with pjson: ${JSON.stringify(initPJSON, null, 2)}`) - await this.savePJSON(initPJSON) + if (await fileExists(this.pjsonPath)) { + return } + + this.debug(`creating ${this.pjsonPath} with pjson: ${JSON.stringify(initPJSON, null, 2)}`) + await this.savePJSON(initPJSON) } private isValidPlugin(p: Config): boolean { @@ -442,7 +446,7 @@ export default class Plugins { spawn(process.argv[0], [rmScript, join(this.config.dataDir, 'node_modules.old')], { detached: true, stdio: 'ignore', - ...(this.config.windows ? {shell: true} : {}), + ...(this.config.windows && {shell: true}), }).unref() } catch (error) { this.debug('Error cleaning up yarn.lock and node_modules:', error) diff --git a/src/spawn.ts b/src/spawn.ts index 72007524..27464995 100644 --- a/src/spawn.ts +++ b/src/spawn.ts @@ -3,7 +3,7 @@ import makeDebug from 'debug' import {spawn as cpSpawn} from 'node:child_process' import {npmRunPathEnv} from 'npm-run-path' -import {LogLevel} from './log-level.js' +import {type LogLevel} from './log-level.js' export type ExecOptions = { cwd: string @@ -54,9 +54,9 @@ export async function spawn(modulePath: string, args: string[] = [], {cwd, logLe const shouldPrint = (str: string): boolean => { // For ux cleanliness purposes, don't print the final line of npm install output if // the log level is 'notice' and there's no other output. - const noOtherOutput = loggedStderr.length === 0 && loggedStdout.length === 0 + const isNoOtherOutput = loggedStderr.length === 0 && loggedStdout.length === 0 const isLastLine = possibleLastLinesOfNpmInstall.some((line) => str.startsWith(line)) - if (noOtherOutput && isLastLine && logLevel === 'notice') { + if (isNoOtherOutput && isLastLine && logLevel === 'notice') { return false } diff --git a/src/util.ts b/src/util.ts index d0f7b107..dfe304ce 100644 --- a/src/util.ts +++ b/src/util.ts @@ -25,5 +25,5 @@ export function uniq(arr: T[]): T[] { } export function uniqWith(arr: T[], fn: (a: T, b: T) => boolean): T[] { - return arr.filter((a, i) => !arr.some((b, j) => j > i && fn(a, b))) + return arr.filter((a, i) => arr.every((b, j) => !(j > i && fn(a, b)))) } diff --git a/src/yarn.ts b/src/yarn.ts index 1139509f..8540499e 100644 --- a/src/yarn.ts +++ b/src/yarn.ts @@ -1,18 +1,18 @@ -import {Errors, Interfaces, ux} from '@oclif/core' +import {Errors, type Interfaces, ux} from '@oclif/core' import makeDebug from 'debug' import {createRequire} from 'node:module' import {fileURLToPath} from 'node:url' -import {LogLevel} from './log-level.js' -import {ExecOptions, Output, spawn} from './spawn.js' +import {type LogLevel} from './log-level.js' +import {type ExecOptions, type Output, spawn} from './spawn.js' const require = createRequire(import.meta.url) const debug = makeDebug('@oclif/plugin-plugins:yarn') export class Yarn { private bin: string | undefined - private config: Interfaces.Config - private logLevel: LogLevel + private readonly config: Interfaces.Config + private readonly logLevel: LogLevel public constructor({config, logLevel}: {config: Interfaces.Config; logLevel: LogLevel}) { this.config = config diff --git a/test/integration/link.integration.ts b/test/integration/link.integration.ts index 109dc69a..cd490af1 100644 --- a/test/integration/link.integration.ts +++ b/test/integration/link.integration.ts @@ -8,7 +8,11 @@ import {join} from 'node:path' async function exec(cmd: string, opts?: {cwd?: string}) { return new Promise((resolve, reject) => { cpExec(cmd, opts, (err, stdout, stderr) => { - if (err) return reject(err) + if (err) { + reject(err) + return + } + resolve({code: 0, stderr, stdout}) }) }) diff --git a/test/plugins.test.ts b/test/plugins.test.ts index f853053f..45f18b2a 100644 --- a/test/plugins.test.ts +++ b/test/plugins.test.ts @@ -1,7 +1,7 @@ -import {Config, Interfaces} from '@oclif/core' +import {Config, type Interfaces} from '@oclif/core' import {expect} from 'chai' import {join} from 'node:path' -import {createSandbox, SinonSandbox, SinonSpy} from 'sinon' +import {createSandbox, type SinonSandbox, type SinonSpy} from 'sinon' import Plugins, {normaliseTag} from '../src/plugins.js' @@ -192,13 +192,13 @@ describe('Plugins', () => { describe('normaliseTag', () => { it('should keep invalid semver tag', async () => { - const cases: { + const cases: Array<{ expected: string expectedModifiedPluginsLength?: number jitPlugins: Record - modifiedPlugins: (Interfaces.LinkedPlugin | Interfaces.UserPlugin)[] + modifiedPlugins: Array tag: string - }[] = [ + }> = [ { expected: 'latest', jitPlugins: {},