diff --git a/eslint.config.mjs b/eslint.config.mjs
index 28d091533f2..f6ac8cd746b 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -8,6 +8,7 @@ import {defineConfig, globalIgnores} from 'eslint/config'
import githubPlugin from 'eslint-plugin-github'
import jest from 'eslint-plugin-jest'
import storybook from 'eslint-plugin-storybook'
+import primerDev from 'eslint-plugin-primer-dev'
import react from 'eslint-plugin-react'
import reactCompiler from 'eslint-plugin-react-compiler'
import reactHooks from 'eslint-plugin-react-hooks'
@@ -118,6 +119,7 @@ const config = defineConfig([
'primer-react/prefer-action-list-item-onselect': 'error',
},
},
+ primerDev.configs.recommended,
{
languageOptions: {
diff --git a/package-lock.json b/package-lock.json
index 56fdf11a3f7..1cb2e3af5c0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -92,7 +92,7 @@
"react-dom": "^18.3.1"
},
"devDependencies": {
- "@primer/react": "37.26.0",
+ "@primer/react": "37.27.0",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^7.11.0",
@@ -445,7 +445,7 @@
"name": "example-nextjs",
"version": "0.0.0",
"dependencies": {
- "@primer/react": "37.26.0",
+ "@primer/react": "37.27.0",
"next": "^15.2.3",
"react": "^19.0.0",
"react-dom": "^19.0.0",
@@ -630,7 +630,7 @@
"version": "0.0.0",
"dependencies": {
"@primer/octicons-react": "^19.14.0",
- "@primer/react": "37.26.0",
+ "@primer/react": "37.27.0",
"clsx": "^2.1.1",
"next": "^14.2.30",
"react": "^18.3.1",
@@ -11268,7 +11268,9 @@
}
},
"node_modules/acorn": {
- "version": "8.14.0",
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"bin": {
@@ -15440,6 +15442,10 @@
"url": "https://opencollective.com/synckit"
}
},
+ "node_modules/eslint-plugin-primer-dev": {
+ "resolved": "packages/eslint-plugin-primer-dev",
+ "link": true
+ },
"node_modules/eslint-plugin-primer-react": {
"version": "6.1.6",
"resolved": "https://registry.npmjs.org/eslint-plugin-primer-react/-/eslint-plugin-primer-react-6.1.6.tgz",
@@ -32499,6 +32505,344 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "packages/eslint-plugin-primer-dev": {
+ "version": "0.0.0",
+ "devDependencies": {
+ "eslint": "^9.29.0"
+ },
+ "peerDependencies": {
+ "eslint": "9.x"
+ }
+ },
+ "packages/eslint-plugin-primer-dev/node_modules/@eslint/config-array": {
+ "version": "0.20.1",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz",
+ "integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/object-schema": "^2.1.6",
+ "debug": "^4.3.1",
+ "minimatch": "^3.1.2"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "packages/eslint-plugin-primer-dev/node_modules/@eslint/js": {
+ "version": "9.29.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.29.0.tgz",
+ "integrity": "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ }
+ },
+ "packages/eslint-plugin-primer-dev/node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "packages/eslint-plugin-primer-dev/node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "packages/eslint-plugin-primer-dev/node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "packages/eslint-plugin-primer-dev/node_modules/eslint": {
+ "version": "9.29.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.29.0.tgz",
+ "integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.20.1",
+ "@eslint/config-helpers": "^0.2.1",
+ "@eslint/core": "^0.14.0",
+ "@eslint/eslintrc": "^3.3.1",
+ "@eslint/js": "9.29.0",
+ "@eslint/plugin-kit": "^0.3.1",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "@types/json-schema": "^7.0.15",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^8.4.0",
+ "eslint-visitor-keys": "^4.2.1",
+ "espree": "^10.4.0",
+ "esquery": "^1.5.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "packages/eslint-plugin-primer-dev/node_modules/eslint-scope": {
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "packages/eslint-plugin-primer-dev/node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "packages/eslint-plugin-primer-dev/node_modules/espree": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.15.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "packages/eslint-plugin-primer-dev/node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "packages/eslint-plugin-primer-dev/node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "packages/eslint-plugin-primer-dev/node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "packages/eslint-plugin-primer-dev/node_modules/flat-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "packages/eslint-plugin-primer-dev/node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "packages/eslint-plugin-primer-dev/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "packages/eslint-plugin-primer-dev/node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "packages/eslint-plugin-primer-dev/node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "packages/eslint-plugin-primer-dev/node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "packages/eslint-plugin-primer-dev/node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "packages/eslint-plugin-primer-dev/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"packages/postcss-preset-primer": {
"version": "0.0.0",
"dependencies": {
@@ -32661,7 +33005,7 @@
},
"packages/react": {
"name": "@primer/react",
- "version": "37.26.0",
+ "version": "37.27.0",
"license": "MIT",
"dependencies": {
"@github/relative-time-element": "^4.4.5",
diff --git a/package.json b/package.json
index de19096d310..f582aae1876 100644
--- a/package.json
+++ b/package.json
@@ -20,7 +20,7 @@
"build:docs:preview": "NODE_OPTIONS=--openssl-legacy-provider script/build-docs preview",
"build:components.json": "npm run build:components.json -w @primer/react",
"build:hooks.json": "npm run build:hooks.json -w @primer/react",
- "lint": "eslint '**/*.{js,ts,tsx,md,mdx}' --max-warnings=0",
+ "lint": "NODE_OPTIONS='--experimental-strip-types' eslint '**/*.{js,ts,tsx,md,mdx}' --max-warnings=0 --flag unstable_native_nodejs_ts_config",
"lint:css": "stylelint --rd -q '**/*.css'",
"lint:css:fix": "npm run lint:css -- --fix",
"lint:fix": "npm run lint -- --fix",
diff --git a/packages/eslint-plugin-primer-dev/package.json b/packages/eslint-plugin-primer-dev/package.json
new file mode 100644
index 00000000000..c38840618d1
--- /dev/null
+++ b/packages/eslint-plugin-primer-dev/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "eslint-plugin-primer-dev",
+ "type": "module",
+ "version": "0.0.0",
+ "exports": "./src/index.ts",
+ "peerDependencies": {
+ "eslint": "9.x"
+ },
+ "devDependencies": {
+ "eslint": "^9.29.0"
+ }
+}
diff --git a/packages/eslint-plugin-primer-dev/src/__tests__/prefer-spread-before-props.test.ts b/packages/eslint-plugin-primer-dev/src/__tests__/prefer-spread-before-props.test.ts
new file mode 100644
index 00000000000..270aabea601
--- /dev/null
+++ b/packages/eslint-plugin-primer-dev/src/__tests__/prefer-spread-before-props.test.ts
@@ -0,0 +1,36 @@
+import {RuleTester} from 'eslint'
+import {test} from 'vitest'
+import {rule} from '../rules/prefer-spread-before-props'
+
+const ruleTester = new RuleTester({
+ languageOptions: {
+ ecmaVersion: 'latest',
+ sourceType: 'module',
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+ },
+})
+
+test('prefer-spread-before-props', () => {
+ ruleTester.run('prefer-spread-before-props', rule, {
+ valid: [
+ {
+ code: '',
+ },
+ ],
+ invalid: [
+ {
+ code: '',
+ output: '',
+ errors: [
+ {
+ messageId: 'spreadBeforeProps',
+ },
+ ],
+ },
+ ],
+ })
+})
diff --git a/packages/eslint-plugin-primer-dev/src/index.ts b/packages/eslint-plugin-primer-dev/src/index.ts
new file mode 100644
index 00000000000..876835a055c
--- /dev/null
+++ b/packages/eslint-plugin-primer-dev/src/index.ts
@@ -0,0 +1,24 @@
+import {rule as preferSpreadBeforeProps} from './rules/prefer-spread-before-props.ts'
+
+const plugin = {
+ meta: 'eslint-plugin-primer-dev',
+ rules: {
+ 'prefer-spread-before-props': preferSpreadBeforeProps,
+ },
+ configs: {},
+}
+
+Object.assign(plugin.configs, {
+ recommended: [
+ {
+ plugins: {
+ 'primer-dev': plugin,
+ },
+ rules: {
+ 'primer-dev/prefer-spread-before-props': 'error',
+ },
+ },
+ ],
+})
+
+export default plugin
diff --git a/packages/eslint-plugin-primer-dev/src/rules/prefer-spread-before-props.ts b/packages/eslint-plugin-primer-dev/src/rules/prefer-spread-before-props.ts
new file mode 100644
index 00000000000..9de33e691ad
--- /dev/null
+++ b/packages/eslint-plugin-primer-dev/src/rules/prefer-spread-before-props.ts
@@ -0,0 +1,46 @@
+import type {Rule} from 'eslint'
+
+export const rule: Rule.RuleModule = {
+ meta: {
+ type: 'problem',
+ fixable: 'code',
+ messages: {
+ spreadBeforeProps: 'Spread attributes must be placed before other props',
+ },
+ },
+ create(context) {
+ return {
+ JSXOpeningElement(node) {
+ const index = node.attributes.findIndex(attribute => {
+ return attribute.type === 'JSXSpreadAttribute'
+ })
+ if (index === -1) {
+ return
+ }
+
+ if (index !== 0) {
+ context.report({
+ node,
+ messageId: 'spreadBeforeProps',
+ fix(fixer) {
+ const {sourceCode} = context
+ const attributes = node.attributes.slice()
+ const [spreadAttribute] = attributes.splice(index, 1)
+ attributes.unshift(spreadAttribute)
+
+ // Get the range from the first attribute to the last attribute
+ const firstAttr = node.attributes[0]
+ const lastAttr = node.attributes[node.attributes.length - 1]
+ const range: [number, number] = [firstAttr.range![0], lastAttr.range![1]]
+
+ // Generate the new attributes text with proper spacing
+ const newAttributesText = attributes.map(attr => sourceCode.getText(attr)).join(' ')
+
+ return fixer.replaceTextRange(range, newAttributesText)
+ },
+ })
+ }
+ },
+ }
+ },
+}
diff --git a/packages/eslint-plugin-primer-dev/vitest.config.ts b/packages/eslint-plugin-primer-dev/vitest.config.ts
new file mode 100644
index 00000000000..e28d08c4e78
--- /dev/null
+++ b/packages/eslint-plugin-primer-dev/vitest.config.ts
@@ -0,0 +1,7 @@
+import {defineConfig} from 'vitest/config'
+
+export default defineConfig({
+ test: {
+ environment: 'node',
+ },
+})