diff --git a/package.json b/package.json index 61de6bd5..5a145590 100644 --- a/package.json +++ b/package.json @@ -53,11 +53,11 @@ "TypeScript" ], "dependencies": { - "@typescript-eslint/parser": "^5.0.0", + "@typescript-eslint/parser": "^5.43.0", "eslint-config-standard": "17.0.0" }, "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^5.0.0", + "@typescript-eslint/eslint-plugin": "^5.43.0", "eslint": "^8.0.1", "eslint-plugin-import": "^2.25.2", "eslint-plugin-n": "^15.0.0", @@ -75,7 +75,11 @@ "@types/node": "18.15.2", "@types/npm-package-arg": "6.1.1", "@types/semver": "7.3.13", + "@types/ungap__structured-clone": "0.3.0", + "@typescript-eslint_bottom/eslint-plugin": "npm:@typescript-eslint/eslint-plugin@5.43.0", + "@typescript-eslint_bottom/parser": "npm:@typescript-eslint/parser@5.43.0", "@typescript-eslint/eslint-plugin": "5.50.0", + "@ungap/structured-clone": "1.0.2", "ava": "5.2.0", "editorconfig-checker": "5.0.1", "eslint": "8.36.0", diff --git a/readme.md b/readme.md index d08df1e8..49a981c6 100644 --- a/readme.md +++ b/readme.md @@ -43,7 +43,7 @@ npm install --save-dev \ eslint-plugin-promise@^6.0.0 \ eslint-plugin-import@^2.25.2 \ eslint-plugin-n@^15.0.0 \ - @typescript-eslint/eslint-plugin@^5.0.0 \ + @typescript-eslint/eslint-plugin@^5.43.0 \ eslint-config-standard-with-typescript@latest ``` diff --git a/src/index.test.ts b/src/index.test.ts index bba79f9e..91e2b22c 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -4,13 +4,14 @@ import configStandard from './eslint-config-standard' import { rules as typescriptEslintRules } from '@typescript-eslint/eslint-plugin' import standardPkg from 'eslint-config-standard/package.json' import type { NormalizedPackageJson, readPackageUp } from 'read-pkg-up' -import { Linter } from 'eslint' +import { Linter, ESLint } from 'eslint' import { readFile } from 'fs/promises' import { resolve } from 'path' import npmPkgArg from 'npm-package-arg' import semver from 'semver' import inclusion from 'inclusion' import { diff as justDiff } from 'just-diff' +import structuredClone from '@ungap/structured-clone' interface PkgDetails { pkgPath: string @@ -34,6 +35,8 @@ const getPkgDetails = async (): Promise => { return { pkgJson: ourPkg, pkgPath: readResult.path, ourDeps, ourPeerDeps, ourDevDeps } } +const extractVersionSpec = (range: string): string => range.split('@').slice(-1)[0] + const equivalents = [...(new Linter()).getRules().keys()] .filter(name => Object.prototype.hasOwnProperty.call(typescriptEslintRules, name)) @@ -297,6 +300,10 @@ const isPinnedRange = (rangeStr: string): boolean => { range.set[0][0].operator === '' } +const typescriptEslintBottom = '@typescript-eslint_bottom' +const typescriptEslintBottomPlugin = `${typescriptEslintBottom}/eslint-plugin` +const typescriptEslintBottomParser = `${typescriptEslintBottom}/parser` + test('Dependencies range types', async (t) => { const { ourDeps, ourPeerDeps, ourDevDeps } = await getPkgDetails() @@ -314,8 +321,9 @@ test('Dependencies range types', async (t) => { ) } } - for (const [name, range] of Object.entries(ourDevDeps)) { - t.true(isPinnedRange(range), `Dev dependency \`${name}: ${range}\` is pinned`) + for (const [name, spec] of Object.entries(ourDevDeps)) { + const range = name.startsWith(`${typescriptEslintBottom}/`) ? extractVersionSpec(spec) : spec + t.true(isPinnedRange(range), `Dev dependency \`${name}: ${spec}\` is pinned`) } }) @@ -495,3 +503,46 @@ test('all plugin rules are considered', (t) => { }) t.deepEqual(inexplicablyExcludedRules, [], 'rules inexplicably excluded') }) + +test('our configuration is compatible with the plugin and parser at bottom of peer dep range', async (t) => { + const { ourPeerDeps, ourDevDeps } = await getPkgDetails() + + const peerDepRange = ourPeerDeps['@typescript-eslint/eslint-plugin'] + if (peerDepRange === undefined) throw new Error() + + const bottomPluginVersion = extractVersionSpec(ourDevDeps[typescriptEslintBottomPlugin]) + const bottomParserVersion = extractVersionSpec(ourDevDeps[typescriptEslintBottomParser]) + + const minPeerDepVersion = semver.minVersion(peerDepRange) + if (minPeerDepVersion === null) throw new Error() + + t.deepEqual(bottomPluginVersion, minPeerDepVersion.version, 'bottom plugin version is bottom of peer dep') + t.deepEqual(bottomParserVersion, minPeerDepVersion.version, 'bottom parser version is bottom of peer dep') + + const config = structuredClone(exported) + + config.parser = typescriptEslintBottomParser + config.plugins = [typescriptEslintBottomPlugin] + + if (config.overrides === undefined) throw new Error() + const overrides = config.overrides[0] + if (overrides === undefined) throw new Error() + + if (overrides.rules === undefined) throw new Error() + + overrides.rules = Object.fromEntries( + Object.entries(overrides.rules).map(([name, config]) => [ + name.replace('@typescript-eslint/', `${typescriptEslintBottom}/`), + config + ]) + ) + + const eslint = new ESLint({ + useEslintrc: false, + overrideConfig: config + }) + + await t.notThrowsAsync(async () => { + await eslint.lintText('foo') + }) +})