Skip to content

Commit

Permalink
Ensure eslint plugins dont conflict (#35667)
Browse files Browse the repository at this point in the history
  • Loading branch information
ijjk committed Mar 28, 2022
1 parent 65680ba commit 12e1bb7
Show file tree
Hide file tree
Showing 3 changed files with 168 additions and 1 deletion.
45 changes: 45 additions & 0 deletions packages/eslint-config-next/index.js
Expand Up @@ -4,6 +4,51 @@
*
* https://www.npmjs.com/package/@rushstack/eslint-patch
*/
const keptPaths = []
const sortedPaths = []
const cwd = process.cwd().replace(/\\/g, '/')
const originalPaths = require.resolve.paths('eslint-plugin-import')

// eslint throws a conflict error when plugins resolve to different
// locations, since we want to lock our dependencies by default
// but also need to allow using user dependencies this updates
// our resolve paths to first check the cwd and iterate to
// eslint-config-next's dependencies if needed

for (let i = originalPaths.length - 1; i >= 0; i--) {
const currentPath = originalPaths[i]

if (currentPath.replace(/\\/g, '/').startsWith(cwd)) {
sortedPaths.push(currentPath)
} else {
keptPaths.unshift(currentPath)
}
}

// maintain order of node_modules outside of cwd
sortedPaths.push(...keptPaths)

const hookPropertyMap = new Map(
[
['eslint-plugin-import', 'eslint-plugin-import'],
['eslint-plugin-react', 'eslint-plugin-react'],
['eslint-plugin-jsx-a11y', 'eslint-plugin-jsx-a11y'],
].map(([request, replacement]) => [
request,
require.resolve(replacement, { paths: sortedPaths }),
])
)

const mod = require('module')
const resolveFilename = mod._resolveFilename
mod._resolveFilename = function (request, parent, isMain, options) {
const hookResolved = hookPropertyMap.get(request)
if (hookResolved) {
request = hookResolved
}
return resolveFilename.call(mod, request, parent, isMain, options)
}

require('@rushstack/eslint-patch/modern-module-resolution')

module.exports = {
Expand Down
6 changes: 5 additions & 1 deletion test/lib/create-next-install.js
Expand Up @@ -51,7 +51,11 @@ async function createNextInstall(

const pkgPaths = await linkPackages(tmpRepoDir)
const combinedDependencies = {
...dependencies,
...Object.keys(dependencies).reduce((prev, pkg) => {
const pkgPath = pkgPaths.get(pkg)
prev[pkg] = pkgPath || dependencies[pkg]
return prev
}, {}),
next: pkgPaths.get('next'),
}

Expand Down
118 changes: 118 additions & 0 deletions test/production/eslint-plugin-deps/index.test.ts
@@ -0,0 +1,118 @@
import { createNext } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import { renderViaHTTP } from 'next-test-utils'

describe('eslint plugin deps', () => {
let next: NextInstance

beforeAll(async () => {
next = await createNext({
files: {
'pages/index.tsx': `export default function Page() {
return <p>hello world</p>;
}
`,
'.eslintrc': `
{
"parser": "@typescript-eslint/parser",
"plugins": ["react", "@typescript-eslint"],
"extends": [
"eslint:recommended",
"next/core-web-vitals",
"plugin:react/recommended",
"plugin:react/jsx-runtime",
"plugin:import/typescript",
"plugin:import/recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"prettier"
],
"env": {
"es2021": true,
"browser": true
},
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module",
"project": ["./tsconfig.json"],
"ecmaFeatures": { "jsx": true }
},
"settings": {
"react": { "version": "detect" },
"import/resolver": { "typescript": {} }
},
"rules": {
"no-else-return": "error",
"semi": ["error", "always"],
"no-useless-rename": "error",
"quotes": ["error", "double"],
"eol-last": ["error", "always"],
"no-console": [2, { "allow": ["warn", "error"] }],
"no-multiple-empty-lines": ["error", { "max": 1 }],
"no-unused-expressions": ["error", { "allowShortCircuit": true, "allowTernary": true, "enforceForJSX": true }],
"import/named": 0,
"import/order": [
"error",
{
"warnOnUnassignedImports": true,
"newlines-between": "always",
"groups": ["builtin", "external", "internal", "parent", ["sibling", "index"], "object", "type"]
}
],
"react/display-name": 0,
"react/prop-types": 0,
"react/react-in-jsx-scope": 0,
"react/self-closing-comp": ["error", { "component": true }],
"react-hooks/exhaustive-deps": ["warn", { "additionalHooks": "useIsomorphicLayoutEffect" }],
"@typescript-eslint/indent": 0,
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/no-var-requires": 0,
"@typescript-eslint/no-use-before-define": 0,
"@typescript-eslint/member-delimiter-style": 0,
"@typescript-eslint/explicit-function-return-type": 0,
"@typescript-eslint/explicit-member-accessibility": 0,
"@typescript-eslint/no-unused-vars": [2, { "argsIgnorePattern": "^_" }],
"@typescript-eslint/array-type": ["error", { "default": "array-simple" }],
"@typescript-eslint/no-unnecessary-boolean-literal-compare": [
"error",
{ "allowComparingNullableBooleansToTrue": false }
]
}
}
`,
},
dependencies: {
'@typescript-eslint/eslint-plugin': '^5.16.0',
'@typescript-eslint/parser': '^5.16.0',
'eslint-config-prettier': '^8.5.0',
'eslint-plugin-import': '^2.25.4',
'eslint-plugin-react': '^7.29.4',
next: '12.1.1',
react: '17.0.2',
'react-dom': '17.0.2',
'@types/node': '17.0.23',
'@types/react': '17.0.43',
'@types/react-dom': '17.0.14',
eslint: '^8.12.0',
'eslint-config-next': '^12.1.1',
typescript: '4.6.3',
},
packageJson: {
scripts: {
build: 'next build --no-lint && next lint',
},
},
buildCommand: 'yarn build',
})
})
afterAll(() => next.destroy())

it('should work', async () => {
const html = await renderViaHTTP(next.url, '/')
expect(html).toContain('hello world')
})
})

0 comments on commit 12e1bb7

Please sign in to comment.