Skip to content

Commit acf65b6

Browse files
feat: add intake method to blockESLint (#2167)
## PR Checklist - [x] Addresses an existing open issue: fixes #2158 - [x] That issue was marked as [`status: accepting prs`](https://github.com/JoshuaKGoldberg/create-typescript-app/issues?q=is%3Aopen+is%3Aissue+label%3A%22status%3A+accepting+prs%22) - [x] Steps in [CONTRIBUTING.md](https://github.com/JoshuaKGoldberg/create-typescript-app/blob/main/.github/CONTRIBUTING.md) were taken ## Overview This is one of the more ridiculous things I've written recently. It: 1. Attempts to parse existing `eslint.config.{js,mjs}` content as a [`@typescript-eslint/typescript-estree`](https://typescript-eslint.io/packages/typescript-estree) tree 2. Find the `{ ignores: [...] }` from its first argument 3. Find the next `{ rules: ... }` from its remaining arguments 4. Parse rules groups using [JSON5.parse](https://www.npmjs.com/package/json5) ...and only if all steps return something looking like what we expect do we give back some data. This is really beyond what I'd want folks to have to write in `intake`. I really wish for this all to be done automatically and reliably: bingo-js/bingo#128. But that's going to be a lot more work I don't have time for soon. 🎁
1 parent 2dd6abe commit acf65b6

12 files changed

+648
-55
lines changed

cspell.json

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
],
1212
"words": [
1313
"Anson",
14+
"TSESTree",
1415
"apexskier",
1516
"attw",
1617
"boop",

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"*": "prettier --ignore-unknown --write"
3838
},
3939
"dependencies": {
40+
"@typescript-eslint/typescript-estree": "^8.29.1",
4041
"bingo": "^0.5.14",
4142
"bingo-fs": "^0.5.5",
4243
"bingo-stratum": "^0.5.11",

pnpm-lock.yaml

+39-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/blocks/blockESLint.test.ts

+48-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
1-
import { testBlock } from "bingo-stratum-testers";
2-
import { describe, expect, test } from "vitest";
1+
import { testBlock, testIntake } from "bingo-stratum-testers";
2+
import { describe, expect, it, test, vi } from "vitest";
33

44
import { blockESLint } from "./blockESLint.js";
55
import { optionsBase } from "./options.fakes.js";
66

7+
const mockIntakeData = { ignores: ["lib"] };
8+
9+
const mockBlockESLintIntake = vi.fn().mockReturnValue(mockIntakeData);
10+
11+
vi.mock("./eslint/blockESLintIntake.js", () => ({
12+
get blockESLintIntake() {
13+
return mockBlockESLintIntake;
14+
},
15+
}));
16+
717
describe("blockESLint", () => {
818
test("without addons or mode", () => {
919
const creation = testBlock(blockESLint, {
@@ -1402,4 +1412,40 @@ describe("blockESLint", () => {
14021412
}
14031413
`);
14041414
});
1415+
1416+
describe("intake", () => {
1417+
it("returns undefined when there is no eslint.config file", () => {
1418+
const actual = testIntake(blockESLint, {
1419+
files: {},
1420+
});
1421+
1422+
expect(actual).toBeUndefined();
1423+
});
1424+
1425+
it("returns data when there is an eslint.config.js file", () => {
1426+
const sourceText = "export default ...";
1427+
1428+
const actual = testIntake(blockESLint, {
1429+
files: {
1430+
"eslint.config.js": [sourceText],
1431+
},
1432+
});
1433+
1434+
expect(mockBlockESLintIntake).toHaveBeenCalledWith(sourceText);
1435+
expect(actual).toBe(mockIntakeData);
1436+
});
1437+
1438+
it("returns data when there is an eslint.config.mjs file", () => {
1439+
const sourceText = "export default ...";
1440+
1441+
const actual = testIntake(blockESLint, {
1442+
files: {
1443+
"eslint.config.mjs": [sourceText],
1444+
},
1445+
});
1446+
1447+
expect(mockBlockESLintIntake).toHaveBeenCalledWith(sourceText);
1448+
expect(actual).toBe(mockIntakeData);
1449+
});
1450+
});
14051451
});

src/blocks/blockESLint.ts

+17-49
Original file line numberDiff line numberDiff line change
@@ -12,57 +12,18 @@ import { blockRemoveDependencies } from "./blockRemoveDependencies.js";
1212
import { blockRemoveFiles } from "./blockRemoveFiles.js";
1313
import { blockRemoveWorkflows } from "./blockRemoveWorkflows.js";
1414
import { blockVSCode } from "./blockVSCode.js";
15+
import { blockESLintIntake } from "./eslint/blockESLintIntake.js";
16+
import {
17+
Extension,
18+
ExtensionRuleGroup,
19+
ExtensionRules,
20+
zExtension,
21+
zExtensionRules,
22+
zPackageImport,
23+
} from "./eslint/schemas.js";
24+
import { intakeFile } from "./intake/intakeFile.js";
1525
import { CommandPhase } from "./phases.js";
1626

17-
const zRuleOptions = z.union([
18-
z.literal("error"),
19-
z.literal("off"),
20-
z.literal("warn"),
21-
z.union([
22-
z.tuple([z.union([z.literal("error"), z.literal("warn")]), z.unknown()]),
23-
z.tuple([
24-
z.union([z.literal("error"), z.literal("warn")]),
25-
z.unknown(),
26-
z.unknown(),
27-
]),
28-
]),
29-
]);
30-
31-
const zExtensionRuleGroup = z.object({
32-
comment: z.string().optional(),
33-
entries: z.record(z.string(), zRuleOptions),
34-
});
35-
36-
type ExtensionRuleGroup = z.infer<typeof zExtensionRuleGroup>;
37-
38-
const zExtensionRules = z.union([
39-
z.record(z.string(), zRuleOptions),
40-
z.array(zExtensionRuleGroup),
41-
]);
42-
43-
type ExtensionRules = z.infer<typeof zExtensionRules>;
44-
45-
const zExtension = z.object({
46-
extends: z.array(z.string()).optional(),
47-
files: z.array(z.string()).optional(),
48-
languageOptions: z.unknown().optional(),
49-
linterOptions: z.unknown().optional(),
50-
plugins: z.record(z.string(), z.string()).optional(),
51-
rules: zExtensionRules.optional(),
52-
settings: z.record(z.string(), z.unknown()).optional(),
53-
});
54-
55-
type Extension = z.infer<typeof zExtension>;
56-
57-
const zPackageImport = z.object({
58-
source: z.union([
59-
z.string(),
60-
z.object({ packageName: z.string(), version: z.string() }),
61-
]),
62-
specifier: z.string(),
63-
types: z.boolean().optional(),
64-
});
65-
6627
export const blockESLint = base.createBlock({
6728
about: {
6829
name: "ESLint",
@@ -76,6 +37,13 @@ export const blockESLint = base.createBlock({
7637
rules: zExtensionRules.optional(),
7738
settings: z.record(z.string(), z.unknown()).optional(),
7839
},
40+
intake({ files }) {
41+
const eslintConfigRaw = intakeFile(files, [
42+
["eslint.config.js", "eslint.config.mjs"],
43+
]);
44+
45+
return eslintConfigRaw ? blockESLintIntake(eslintConfigRaw[0]) : undefined;
46+
},
7947
produce({ addons, options }) {
8048
const { explanations, extensions, ignores, imports, rules, settings } =
8149
addons;

0 commit comments

Comments
 (0)