Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/remove-cli-eslint-config-dependencies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/eslint-plugin-cli': major
Comment thread
gonzaloriestra marked this conversation as resolved.
---

Limit the bundled config to CLI custom rules and remove its shared ESLint dependency tree.
3 changes: 0 additions & 3 deletions .github/workflows/tests-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,6 @@ jobs:
- name: Oxlint
run: pnpm lint:oxlint
if: ${{ matrix.os == 'ubuntu-latest' && matrix.node == '26.1.0' }}
- name: ESLint compatibility
run: pnpm lint:eslint --output-style=stream
if: ${{ matrix.os == 'ubuntu-latest' && matrix.node == '26.1.0' }}
- name: Type-check
run: pnpm nx run-many --all --target=type-check --output-style=stream
if: ${{ matrix.os == 'ubuntu-latest' && matrix.node == '26.1.0' }}
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/tests-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,6 @@ jobs:
run: pnpm prettier
- name: Oxlint
run: pnpm lint:oxlint
- name: ESLint compatibility
run: pnpm lint:eslint --output-style=stream

bundle:
name: 'Bundle'
Expand Down
6 changes: 0 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,6 @@ web_modules/
# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional stylelint cache
.stylelintcache

Expand Down Expand Up @@ -175,9 +172,6 @@ testing.mjs
# NX
.nx

# ESLint reports
eslint-report.json

# Generated docs
docs/api
docs-shopify.dev/commands/interfaces/
Expand Down
2 changes: 1 addition & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"recommendations": ["esbenp.prettier-vscode","dbaeumer.vscode-eslint","nrwl.angular-console","vitest.explorer"]
"recommendations": ["esbenp.prettier-vscode", "oxc.oxc-vscode", "nrwl.angular-console", "vitest.explorer"]
}
5 changes: 2 additions & 3 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"**/node_modules": true
},
"editor.formatOnSave": false,
"eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"],
"oxc.enable": true,
"[javascript]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
Expand All @@ -31,9 +31,8 @@
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
"source.fixAll.oxc": "explicit"
},
"eslint.workingDirectories": [{ "pattern": "packages/*" }],
"vitest.enable": true,
"testing.automaticallyOpenPeekView": "never",
"javascript.preferences.importModuleSpecifierEnding": "js",
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Read the guides that apply to your task.
- [Conventions](docs/cli/conventions.md): follow shared patterns for modules, state, resource cleanup, and file IO.
- [Cross-OS compatibility](docs/cli/cross-os-compatibility.md): avoid OS-specific failures when working with paths, processes, and dependencies.
- [Debugging](docs/cli/debugging.md): investigate failures with the debugger and check diagnostics for credential leaks.
- [ESLint rules](docs/cli/eslint-rules.md): understand local lint rules for command flags and environment variables.
- [Oxlint rules](docs/cli/oxlint-rules.md): understand local lint rules for command flags and environment variables.
- [FAQ](docs/cli/faq.md): understand the choice of TOML for configuration files.
- [Get started](docs/cli/get-started.md): set up the repository and run the CLI against a local project.
- [Naming conventions](docs/cli/naming-conventions.md): use reserved command names, flags, and short forms consistently.
Expand Down
113 changes: 113 additions & 0 deletions bin/audit-oxlint-coverage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import {existsSync, readFileSync, writeFileSync} from 'node:fs'
import {matchesGlob, resolve} from 'node:path'
import {execFileSync} from 'node:child_process'

const root = execFileSync('git', ['rev-parse', '--show-toplevel'], {encoding: 'utf8'}).trim()
const read = (file) => JSON.parse(readFileSync(resolve(root, file), 'utf8'))
const baseline = read('configurations/oxlint-baseline.json')
const mapping = read('configurations/oxlint-rule-mapping.json')
const config = read('oxlint.json')
const scripts = read('package.json').scripts
const formattingInputs = [...scripts.prettier.matchAll(/"([^"]+)"/g)].map((match) => match[1])
const match = (file, patterns) => patterns?.some((pattern) => matchesGlob(file, pattern))
const severity = (value) => {
const level = Array.isArray(value) ? value[0] : value
return {off: 0, allow: 0, warn: 1, error: 2, deny: 2}[level] ?? level
}

const stable = (value) =>
JSON.stringify(value, (_, item) =>
item && typeof item === 'object' && !Array.isArray(item)
? Object.fromEntries(Object.entries(item).sort(([first], [second]) => first.localeCompare(second)))
: item,
)

const missing = new Map()
const options = new Set()
const severities = new Set()
const unsupported = new Set()
const supported = new Set()
const partial = new Set()
const missingFormatting = []
let filesCompared = 0
const retiredFiles = []
for (const group of baseline.configurations) {
const before = {...baseline.rules, ...group.rules}
for (const name of group.removed) delete before[name]
for (const file of group.files) {
if (!existsSync(resolve(root, file))) {
retiredFiles.push(file)
continue
}
const after = {...config.rules}
for (const override of config.overrides) {
if (match(file, override.files) && !match(file, override.excludeFiles)) Object.assign(after, override.rules)
}
// Preserve file-wide exemptions while comparing effective file scopes.
const header =
readFileSync(resolve(root, file), 'utf8').match(/^(?:\s*(?:\/\/[^\n]*(?:\n|$)|\/\*[\s\S]*?\*\/))*/)?.[0] ?? ''
for (const directive of header.matchAll(/eslint-disable(?!-)([^\n*]*)/g)) {
for (const name of directive[1]
.split(' -- ')[0]
.split(',')
.map((name) => name.trim()))
delete after[name]
}
if (!match(file, formattingInputs)) missingFormatting.push(file)
for (const [name, value] of Object.entries(before)) {
const replacement = mapping[name]
if (!replacement) throw new Error(`Undocumented original rule: ${name}`)
if (replacement.status === 'unsupported') {
unsupported.add(name)
continue
}
if (severity(after[replacement.replacement] ?? 'off') === 0) {
const examples = missing.get(name) ?? []
if (examples.length < 4) examples.push(file)
missing.set(name, examples)
continue
}
supported.add(name)
if (replacement.status === 'partial') partial.add(name)
if (severity(value) !== severity(after[replacement.replacement])) severities.add(name)
const parameters = (configuration) => (Array.isArray(configuration) ? configuration.slice(1) : [])
if (stable(parameters(value)) !== stable(parameters(after[replacement.replacement]))) options.add(name)
}
filesCompared++
}
}

// Other tools can cover only part of a rule or its file scope.
const otherToolCoverage = Object.fromEntries(
[...unsupported]
.filter((name) => mapping[name].otherToolCoverage)
.map((name) => [name, mapping[name].otherToolCoverage]),
)

const summary = {
baselineCommit: baseline.baselineCommit,
filesCompared,
retiredFiles: retiredFiles.length,
baselineLintRules: Object.keys(mapping).length,
rulesWithReplacements: supported.size,
partialReplacements: partial.size,
rulesWithoutDedicatedReplacement: unsupported.size,
rulesWithOtherToolCoverage: Object.keys(otherToolCoverage).length,
rulesWithScopeGaps: missing.size,
optionDifferences: options.size,
severityDifferences: severities.size,
missingFormattingFiles: missingFormatting.length,
}
const findings = {
scopeGaps: Object.fromEntries(missing),
optionDifferences: [...options].sort(),
severityDifferences: [...severities].sort(),
rulesWithoutDedicatedReplacement: [...unsupported].sort(),
otherToolCoverage,
partialReplacements: [...partial].sort(),
missingFormatting,
}
const output = process.argv[2] ?? '/tmp/cli-oxlint-coverage-audit.json'
writeFileSync(output, JSON.stringify({summary, findings}, null, 2) + '\n')
console.log(JSON.stringify(summary, null, 2))
if (missing.size || missingFormatting.length || severities.size) process.exitCode = 1
Loading
Loading