这是indexloc提供的服务,不要输入任何密码
Skip to content

chore(linting): implement ESLint flat config compatibility for `eslin… #8606

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

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 52 additions & 3 deletions packages/eslint-plugin-turbo/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# `eslint-plugin-turbo`

Ease configuration for Turborepo
Easy ESLint configuration for Turborepo

## Installation

Expand All @@ -16,7 +16,7 @@ npm install eslint --save-dev
npm install eslint-plugin-turbo --save-dev
```

## Usage
## Usage (Legacy `eslintrc*`)

Add `turbo` to the plugins section of your `.eslintrc` configuration file. You can omit the `eslint-plugin-` prefix:

Expand All @@ -36,7 +36,7 @@ Then configure the rules you want to use under the rules section.
}
```

### Example
## Example (Legacy `eslintrc*`)

```json
{
Expand All @@ -51,3 +51,52 @@ Then configure the rules you want to use under the rules section.
}
}
```

## Usage (Flat Config `eslint.config.js`)

In ESLint v8, both the legacy system and the new flat config system are supported. In Eslint v9, only the new system will be supported. See the [official ESLint docs](https://eslint.org/docs/latest/use/configure/configuration-files).

```js
import turbo from "eslint-plugin-turbo";

export default [turbo.configs["flat/recommended"]];
```

Otherwise, you may configure the rules you want to use under the rules section.

```js
import turbo from "eslint-plugin-turbo";

export default [
{
plugins: {
turbo,
},
rules: {
"turbo/no-undeclared-env-vars": "error",
},
},
];
```

## Example (Flat Config `eslint.config.js`)

```js
import turbo from "eslint-plugin-turbo";

export default [
{
plugins: {
turbo,
},
rules: {
"turbo/no-undeclared-env-vars": [
"error",
{
allowList: ["^ENV_[A-Z]+$"],
},
],
},
},
];
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const turbo = require("eslint-plugin-turbo");

module.exports = [turbo.configs["flat/recommended"]];
42 changes: 29 additions & 13 deletions packages/eslint-plugin-turbo/__tests__/cwd.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import path from "node:path";
import { execSync } from "node:child_process";
import JSON5, { parse, stringify } from "json5";
import { type Schema } from "@turbo/types";
import { parse, stringify } from "json5";
import { setupTestFixtures } from "@turbo/test-utils";
import { describe, it, expect } from "@jest/globals";

const env: NodeJS.ProcessEnv = {
...process.env,
ESLINT_USE_FLAT_CONFIG: "false",
};

describe("eslint settings check", () => {
const { useFixture } = setupTestFixtures({
directory: path.join(__dirname, "../"),
Expand All @@ -14,11 +19,15 @@ describe("eslint settings check", () => {
const { root: cwd } = useFixture({ fixture: "workspace" });
execSync(`npm install`, { cwd });

const configString = execSync(`npm exec eslint -- --print-config peer.js`, {
cwd,
encoding: "utf8",
});
const configJson: Record<string, unknown> = parse(configString);
const configString = execSync(
`npm exec eslint -- -c .eslintrc.js --print-config peer.js`,
{
cwd,
encoding: "utf8",
env,
}
);
const configJson = JSON5.parse(configString);

expect(configJson.settings).toEqual({
turbo: {
Expand Down Expand Up @@ -72,10 +81,11 @@ describe("eslint settings check", () => {

const cwd = path.join(root, "child");
const configString = execSync(
`npm exec eslint -- --print-config child.js`,
`npm exec eslint -- -c ../.eslintrc.js --print-config child.js`,
{
cwd,
encoding: "utf8",
env,
}
);
const configJson: Record<string, unknown> = parse(configString);
Expand Down Expand Up @@ -141,9 +151,10 @@ describe("eslint cache is busted", () => {

const cwd = path.join(root, "child");
try {
execSync(`npm exec eslint -- --format=json child.js`, {
execSync(`npm exec eslint -- -c ../.eslintrc.js --format=json child.js`, {
cwd,
encoding: "utf8",
env,
});
} catch (error: unknown) {
const outputJson: Record<string, unknown> = parse(
Expand All @@ -169,11 +180,16 @@ describe("eslint cache is busted", () => {
}

// test that we invalidated the eslint cache
const output = execSync(`npm exec eslint -- --format=json child.js`, {
cwd,
encoding: "utf8",
});
const outputJson: Record<string, unknown> = parse(output);
const output = execSync(
`npm exec eslint -- -c ../.eslintrc.js --format=json child.js`,
{
cwd,
encoding: "utf8",
env,
}
);
const outputJson = JSON5.parse(output);

expect(outputJson).toMatchObject([{ errorCount: 0 }]);
});
});
194 changes: 194 additions & 0 deletions packages/eslint-plugin-turbo/__tests__/cwdFlat.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import path from "path";
import JSON5 from "json5";
import { execSync } from "child_process";
import { Schema } from "@turbo/types";
import { setupTestFixtures } from "@turbo/test-utils";

const env: NodeJS.ProcessEnv = {
...process.env,
ESLINT_USE_FLAT_CONFIG: "true",
};

describe("flat eslint settings check", () => {
const { useFixture } = setupTestFixtures({
directory: path.join(__dirname, "../"),
});

it("does the right thing for peers", () => {
const { root: cwd } = useFixture({ fixture: "workspace" });
execSync(`npm install`, { cwd });

const configString = execSync(
`npm exec eslint -- -c eslint.config.js --print-config peer.js`,
{
cwd,
encoding: "utf8",
env,
}
);
const configJson = JSON5.parse(configString);

expect(configJson.settings).toEqual({
turbo: {
cacheKey: {
global: {
legacyConfig: [],
env: ["CI", "UNORDERED"],
passThroughEnv: null,
dotEnv: {
filePaths: [".env", "missing.env"],
hashes: {
".env": "9ad6c5fd4d5bbe7c00e1f2b358ac7ef2aa3521d0",
},
},
},
globalTasks: {
build: {
legacyConfig: [],
env: [],
passThroughEnv: null,
dotEnv: null,
},
test: {
legacyConfig: [],
env: [],
passThroughEnv: null,
dotEnv: null,
},
lint: {
legacyConfig: [],
env: [],
passThroughEnv: null,
dotEnv: null,
},
deploy: {
legacyConfig: [],
env: [],
passThroughEnv: null,
dotEnv: null,
},
},
workspaceTasks: {},
},
},
});
});

it("does the right thing for child dirs", () => {
const { root } = useFixture({ fixture: "workspace" });
execSync(`npm install`, { cwd: root });

const cwd = path.join(root, "child");
const configString = execSync(
`npm exec eslint -- -c ../eslint.config.js --print-config child.js`,
{
cwd,
encoding: "utf8",
env,
}
);
const configJson = JSON5.parse(configString);

expect(configJson.settings).toEqual({
turbo: {
cacheKey: {
global: {
legacyConfig: [],
env: ["CI", "UNORDERED"],
passThroughEnv: null,
dotEnv: {
filePaths: [".env", "missing.env"],
hashes: {
".env": "9ad6c5fd4d5bbe7c00e1f2b358ac7ef2aa3521d0",
},
},
},
globalTasks: {
build: {
legacyConfig: [],
env: [],
passThroughEnv: null,
dotEnv: null,
},
test: {
legacyConfig: [],
env: [],
passThroughEnv: null,
dotEnv: null,
},
lint: {
legacyConfig: [],
env: [],
passThroughEnv: null,
dotEnv: null,
},
deploy: {
legacyConfig: [],
env: [],
passThroughEnv: null,
dotEnv: null,
},
},
workspaceTasks: {},
},
},
});
});
});

describe("flat eslint cache is busted", () => {
const { useFixture } = setupTestFixtures({
directory: path.join(__dirname, "../"),
});

it("catches a lint error after changing config", () => {
expect.assertions(2);

// ensure that we populate the cache with a failure.
const { root, readJson, write } = useFixture({ fixture: "workspace" });
execSync(`npm install`, { cwd: root });

const cwd = path.join(root, "child");
try {
execSync(
`npm exec eslint -- -c ../eslint.config.js --format=json child.js`,
{
cwd,
encoding: "utf8",
env,
}
);
} catch (error: any) {
const outputJson = JSON5.parse(error.stdout);
expect(outputJson).toMatchObject([
{
messages: [
{
message:
"NONEXISTENT is not listed as a dependency in turbo.json",
},
],
},
]);
}

// change the configuration
const turboJson = readJson<Schema>("turbo.json");
if (turboJson && "globalEnv" in turboJson) {
turboJson.globalEnv = ["CI", "NONEXISTENT"];
write("turbo.json", JSON5.stringify(turboJson, null, 2));
}

// test that we invalidated the eslint cache
const output = execSync(
`npm exec eslint -- -c ../eslint.config.js --format=json child.js`,
{
cwd,
encoding: "utf8",
env,
}
);
const outputJson = JSON5.parse(output);
expect(outputJson).toMatchObject([{ errorCount: 0 }]);
});
});
19 changes: 19 additions & 0 deletions packages/eslint-plugin-turbo/lib/configs/flat/recommended.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { Linter } from "eslint";
import { RULES } from "../../constants";
import { Project } from "../../utils/calculate-inputs";

const project = new Project(process.cwd());
const cacheKey = project.valid() ? project.key() : Math.random();

const config = {
rules: {
[`turbo/${RULES.noUndeclaredEnvVars}`]: "error",
},
settings: {
turbo: {
cacheKey,
},
},
} satisfies Linter.FlatConfig;

export default config;
3 changes: 2 additions & 1 deletion packages/eslint-plugin-turbo/lib/configs/recommended.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Linter } from "eslint";
import { RULES } from "../constants";
import { Project } from "../utils/calculate-inputs";

Expand All @@ -16,6 +17,6 @@ const config = {
rules: {
[`turbo/${RULES.noUndeclaredEnvVars}`]: "error",
},
};
} satisfies Linter.Config;

export default config;
Loading
Loading