Skip to content

Commit 96567e2

Browse files
committed
Update ESLint to v10.0.2
1 parent 2da60bf commit 96567e2

File tree

8 files changed

+117
-46
lines changed

8 files changed

+117
-46
lines changed

.dockerignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
!.clang-format
33
!.editorconfig
44
!entry.ts
5-
!eslint.config.js
5+
!eslint.config.mjs
66
!ruff.toml
77
!stylesheet.xml
88
!svgo.config.js

.editorconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ indent_style=tab
1010
indent_size=2
1111
max_line_length=100
1212

13-
[*.{js,jsx,ts,tsx}]
13+
[*.{js,jsx,mjs,ts,tsx}]
1414
indent_size=2
1515
max_line_length=80
1616

Dockerfile

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -58,24 +58,12 @@ echo 'source /black21-venv/bin/activate && black "$@"' > /usr/bin/black21
5858
chmod +x /usr/bin/black21
5959

6060
# Install Node dependencies
61-
#
62-
# We stay on eslint-plugin-unicorn 56.0.1 because 57+ removed support for
63-
# importing this plugin into the ESLint config as CommonJS. (We use CommonJS
64-
# instead of ESM in the ESLint config mainly because the latter requires a new
65-
# local package.json file that declares `"type": "module"`, which interferes
66-
# with other tools like SVGO). I couldn't get the `deasync` hack to work - it
67-
# just hung forever. TODO: Try updating this plugin after updating Node.js to
68-
# v22+, which has experimental support for synchronously require()-ing ESM?
69-
# https://github.com/sindresorhus/eslint-plugin-unicorn/releases/tag/v57.0.0
70-
# https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c
71-
# https://nodejs.org/en/blog/announcements/v22-release-announce#support-requireing-synchronous-esm-graphs
72-
# https://github.com/eslint/eslint/issues/13684#issuecomment-722949152
7361
npm install -g \
7462
@prettier/plugin-xml@3.4.2 \
75-
eslint@9.39.2 \
63+
eslint@10.0.2 \
7664
eslint-plugin-jsdoc@62.7.1 \
77-
eslint-plugin-sort-keys@2.3.5 \
78-
eslint-plugin-unicorn@56.0.1 \
65+
eslint-plugin-perfectionist@5.6.0 \
66+
eslint-plugin-unicorn@63.0.0 \
7967
prettier@3.8.1 \
8068
svgo@4.0.1 \
8169
typescript-eslint@8.56.1
@@ -132,3 +120,5 @@ COPY --from=entry /entry.js /entry
132120
ENV COURSIER_CACHE=/tmp/coursier-cache
133121
ENV COURSIER_JVM_CACHE=/tmp/coursier-jvm-cache
134122
ENV NODE_PATH=/usr/local/lib/node_modules
123+
# .mjs import resolution doesn't respect NODE_PATH
124+
RUN ln -s /usr/local/lib/node_modules /node_modules

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ This repo contains [pre-commit](https://pre-commit.com/) hooks for Duolingo deve
77
The main hook that runs several code formatters in parallel:
88

99
- [Prettier](https://github.com/prettier/prettier) v3.8.1 for CSS, HTML, JS, JSX, Markdown, Sass, TypeScript, XML, YAML
10-
- [ESLint](https://eslint.org/) v9.39.2 for JS, TypeScript
10+
- [ESLint](https://eslint.org/) v10.0.2 for JS, TypeScript
1111
- [Ruff](https://docs.astral.sh/ruff/) v0.15.5 for Python 3
1212
- [Black](https://github.com/psf/black) v21.12b0 for Python 2
1313
- [autoflake](https://github.com/myint/autoflake) v1.7.8 for Python <!-- TODO: Upgrade to v2+, restrict to Python 2, and reenable Ruff rule F401 once our Python 3 repos that were converted from Python 2 no longer use type hint comments: https://github.com/PyCQA/autoflake/issues/222#issuecomment-1419089254 -->

entry.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ const HOOKS: Record<HookName, Hook> = {
183183
"eslint",
184184
"--fix",
185185
"--config",
186-
"/eslint.config.js",
186+
"/eslint.config.mjs",
187187
...sources,
188188
);
189189
} catch {
@@ -192,7 +192,7 @@ const HOOKS: Record<HookName, Hook> = {
192192
}
193193
},
194194
exclude: MINIFIED_JS_REGEX,
195-
include: /\.[jt]sx?$/,
195+
include: /\.(mjs|[jt]sx?)$/,
196196
runAfter: [HookName.Sed],
197197
},
198198
[HookName.Gofmt]: {
@@ -255,7 +255,7 @@ const HOOKS: Record<HookName, Hook> = {
255255
},
256256
[HookName.Prettier]: {
257257
action: sources => run("prettier", ...PRETTIER_OPTIONS, ...sources),
258-
include: /\.(css|html?|markdown|md|scss|tsx?|ya?ml)$/,
258+
include: /\.(css|html?|markdown|md|mjs|scss|tsx?|ya?ml)$/,
259259
runAfter: [HookName.Sed, HookName.EsLint],
260260
},
261261
[HookName.PrettierJs]: {
@@ -490,7 +490,11 @@ const HOOKS: Record<HookName, Hook> = {
490490

491491
/** Files that match this pattern should never be processed */
492492
const GLOBAL_EXCLUDES = (() => {
493-
const FOLDER_EXCLUDES = [".claude/skills/.generated", "build", "node_modules"];
493+
const FOLDER_EXCLUDES = [
494+
".claude/skills/.generated",
495+
"build",
496+
"node_modules",
497+
];
494498
const FILE_EXCLUDES = [
495499
"AGENTS.md", // Generated by sync-ai-rules pre-commit hook
496500
"copilot-instructions.md", // Generated by sync-ai-rules pre-commit hook

eslint.config.js renamed to eslint.config.mjs

Lines changed: 50 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
const { defineConfig } = require("eslint/config");
2-
const jsdoc = require("eslint-plugin-jsdoc");
3-
const sortKeys = require("eslint-plugin-sort-keys");
4-
const unicorn = require("eslint-plugin-unicorn");
5-
const tseslint = require("typescript-eslint");
1+
import { defineConfig } from "eslint/config";
2+
import perfectionist from "eslint-plugin-perfectionist";
3+
import { Alphabet } from "eslint-plugin-perfectionist/alphabet";
4+
import jsdoc from "eslint-plugin-jsdoc";
5+
import unicorn from "eslint-plugin-unicorn";
6+
import tseslint from "typescript-eslint";
67

78
const config = {
8-
files: ["**/*.{js,jsx,ts,tsx}"],
9+
files: ["**/*.{js,jsx,mjs,ts,tsx}"],
910
languageOptions: { parser: tseslint.parser },
1011
linterOptions: {
1112
// Individual repos may have their own additional ESLint setups that enable
@@ -19,7 +20,7 @@ const config = {
1920
plugins: {
2021
"@typescript-eslint": tseslint.plugin,
2122
jsdoc,
22-
"sort-keys": sortKeys,
23+
perfectionist,
2324
unicorn,
2425
},
2526
//
@@ -80,17 +81,7 @@ const config = {
8081
// "prefer-object-has-own": "error",
8182
// "prefer-object-spread": "error",
8283
"prefer-template": "error",
83-
// This only sorts members within an individual import statement, not import
84-
// statements ("declarations") themselves. We disable that because this
85-
// rule sorts in a weird way: by first member rather than by module name.
86-
// The `import/order` rule provided by eslint-plugin-import does sort
87-
// declarations by module name, but we forgo that too because it groups
88-
// declarations based on environmental factors (e.g. node_modules, Node
89-
// version) that we can't easily determine or reproduce here in a
90-
// repo-agnostic way. One compromise might be to use `import/order` and
91-
// simply disable its regrouping feature in favor of whatever groups are
92-
// found in the source code to be formatted, but no such option exists :/
93-
"sort-imports": ["error", { ignoreDeclarationSort: true }],
84+
// "sort-imports": "error", // Replaced by perfectionist/sort-imports and perfectionist/sort-named-imports
9485
"sort-vars": "error",
9586
// "strict": "error",
9687
// "unicode-bom": "error",
@@ -132,8 +123,28 @@ const config = {
132123
// "jsdoc/tag-lines": "error",
133124
// "jsdoc/text-escaping": "error",
134125

135-
// sort-keys rules. https://github.com/namnm/eslint-plugin-sort-keys
136-
"sort-keys/sort-keys-fix": ["error", "asc", { natural: true }],
126+
// perfectionist rules. https://perfectionist.dev/rules
127+
// "perfectionist/sort-enums": "error", // Reordering can change numeric enum values
128+
"perfectionist/sort-heritage-clauses": "error",
129+
"perfectionist/sort-imports": [
130+
"error",
131+
{
132+
groups: [], // Don't priorize type imports above value imports
133+
newlinesBetween: "ignore",
134+
newlinesInside: "ignore",
135+
},
136+
],
137+
"perfectionist/sort-interfaces": "error",
138+
"perfectionist/sort-intersection-types": "error",
139+
"perfectionist/sort-named-exports": "error",
140+
"perfectionist/sort-named-imports": [
141+
"error",
142+
{ partitionByNewLine: false },
143+
],
144+
"perfectionist/sort-object-types": "error",
145+
"perfectionist/sort-objects": "error",
146+
// "perfectionist/sort-switch-case" // TODO: Enable once it supports partitionByNewLine
147+
"perfectionist/sort-union-types": "error",
137148

138149
// typescript-eslint rules. We exclude rules that require type info because
139150
// they're prohibitively slow. Get the list of autofixable rules by running
@@ -189,10 +200,9 @@ const config = {
189200
// "unicorn/new-for-builtins": "error",
190201
// "unicorn/no-array-for-each": "error", // Bug: fixer deletes comments
191202
// "unicorn/no-array-method-this-argument": "error",
192-
// "unicorn/no-array-push-push": "error", // Bug: fixer deletes comments
193203
// "unicorn/no-await-expression-member": "error",
194204
"unicorn/no-console-spaces": "error",
195-
// "unicorn/no-for-loop": "error", // Bug: https://github.com/sindresorhus/eslint-plugin-unicorn/issues/1802
205+
"unicorn/no-for-loop": "error",
196206
// "unicorn/no-hex-escape": "error",
197207
// "unicorn/no-lonely-if": "error", // Bug: Moves comments around
198208
"unicorn/no-negated-condition": "error",
@@ -249,6 +259,7 @@ const config = {
249259
"unicorn/prefer-regexp-test": "error",
250260
// "unicorn/prefer-set-has": "error",
251261
"unicorn/prefer-set-size": "error",
262+
// "unicorn/prefer-single-call": "error", // Bug: fixer deletes comments
252263
// "unicorn/prefer-spread": "error", // Bug: https://github.com/sindresorhus/eslint-plugin-unicorn/issues/2041
253264
// "unicorn/prefer-string-raw": "error",
254265
// "unicorn/prefer-string-replace-all": "error",
@@ -270,6 +281,22 @@ const config = {
270281

271282
/* eslint-enable */
272283
},
284+
settings: {
285+
perfectionist: {
286+
// By default, perfectionist sorts lowercase before uppercase. We reverse
287+
// that behavior here to match our existing pre-2026 convention, which is
288+
// also arguably more sensible from a coding perspective because it
289+
// prioritizes SCREAMING_SNAKE_CASE constants, similar to how such
290+
// constants are typically defined first in a source file
291+
alphabet: Alphabet.generateRecommendedAlphabet()
292+
.placeAllWithCaseBeforeAllWithOtherCase("uppercase")
293+
.getCharacters(),
294+
ignoreCase: false,
295+
order: "asc",
296+
partitionByNewLine: true,
297+
type: "custom",
298+
},
299+
},
273300
};
274301

275-
module.exports = defineConfig([config]);
302+
export default defineConfig([config]);

test/after/hello.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,31 @@ try {
2121
console.log("hi", `world${ex}`);
2222
}
2323
}
24+
25+
interface Serializable {
26+
serialize(): string;
27+
}
28+
interface Printable {
29+
print(): void;
30+
}
31+
class Report implements Printable, Serializable {
32+
print(): void {}
33+
serialize(): string {
34+
return "";
35+
}
36+
}
37+
38+
interface Options {
39+
Cache: boolean;
40+
Format: string;
41+
debug: boolean;
42+
verbose: boolean;
43+
}
44+
45+
type Combined = Options & Printable & Serializable;
46+
47+
type Level = "error" | "info" | "warn";
48+
49+
type Point = { x: number; y: number };
50+
51+
export type { Combined, Level, Options, Point };

test/before/hello.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11

2-
import { execSync , execFile } from "child_process" ;
32
import { writeFile, FSWatcher, readFile } from "fs" ;
3+
import { execSync , execFile } from "child_process" ;
44

55
import { foo } from "./bar";
66

@@ -25,3 +25,25 @@ try {
2525
}catch( err) {
2626
if (8.00 > foo!!) console.log("hi ", "world" + err);
2727
}
28+
29+
interface Serializable { serialize(): string }
30+
interface Printable { print(): void }
31+
class Report implements Serializable, Printable {
32+
print(): void {}
33+
serialize(): string { return "" }
34+
}
35+
36+
interface Options {
37+
verbose: boolean;
38+
Format: string;
39+
debug: boolean;
40+
Cache: boolean;
41+
}
42+
43+
type Combined = Printable & Options & Serializable;
44+
45+
type Level = "warn" | "error" | "info";
46+
47+
type Point = { y: number; x: number };
48+
49+
export type { Options, Combined, Level, Point };

0 commit comments

Comments
 (0)