Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure eslint plugins dont conflict #35667

Merged
merged 1 commit into from Mar 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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')
})
})