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

New testing helpers using using, replace old console mocking functions. #11177

Merged
merged 14 commits into from
Sep 14, 2023
Merged
18 changes: 14 additions & 4 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint", "import"],
"plugins": ["@typescript-eslint", "import", "local-rules"],
"env": {
"browser": true,
"node": true,
Expand Down Expand Up @@ -52,15 +52,25 @@
"ignorePackages": true,
"checkTypeImports": true
}
]
],
"local-rules/require-using-disposable": "error"
}
},
{
"files": ["**/__tests__/**/*.[jt]sx", "**/?(*.)+(test).[jt]sx"],
"files": [
"**/__tests__/**/*.[jt]s",
"**/__tests__/**/*.[jt]sx",
"**/?(*.)+(test).[jt]s",
"**/?(*.)+(test).[jt]sx"
],
"extends": ["plugin:testing-library/react"],
"parserOptions": {
"project": "./tsconfig.tests.json"
},
"rules": {
"testing-library/prefer-user-event": "error",
"testing-library/no-wait-for-multiple-assertions": "off"
"testing-library/no-wait-for-multiple-assertions": "off",
"local-rules/require-using-disposable": "error"
}
}
],
Expand Down
8 changes: 3 additions & 5 deletions config/version.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,9 @@ switch (process.argv[2]) {
}

case "verify": {
const { ApolloClient, InMemoryCache } = require(path.join(
distRoot,
"core",
"core.cjs"
));
const { ApolloClient, InMemoryCache } = require(
path.join(distRoot, "core", "core.cjs")
);

// Though this may seem like overkill, verifying that ApolloClient is
// constructible in Node.js is actually pretty useful, too!
Expand Down
Empty file.
Empty file.
7 changes: 7 additions & 0 deletions eslint-local-rules/fixtures/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"compilerOptions": {
"strict": true,
"target": "esnext"
},
"include": ["file.ts", "react.tsx"]
}
14 changes: 14 additions & 0 deletions eslint-local-rules/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
require("ts-node").register({
transpileOnly: true,
compilerOptions: {
// we need this to be nodenext in the tsconfig, because
// @typescript-eslint/utils only seems to export ESM
// in TypeScript's eyes, but it totally works
module: "commonjs",
moduleResolution: "node",
},
});

module.exports = {
"require-using-disposable": require("./require-using-disposable").rule,
};
5 changes: 5 additions & 0 deletions eslint-local-rules/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"scripts": {
"test": "node -r ts-node/register/transpile-only --no-warnings --test --watch *.test.ts"
}
}
31 changes: 31 additions & 0 deletions eslint-local-rules/require-using-disposable.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { rule } from "./require-using-disposable";
import { ruleTester } from "./testSetup";

ruleTester.run("require-using-disposable", rule, {
valid: [
`
function foo(): Disposable {}
using bar = foo()
`,
`
function foo(): AsyncDisposable {}
await using bar = foo()
`,
],
invalid: [
{
code: `
function foo(): Disposable {}
const bar = foo()
`,
errors: [{ messageId: "missingUsing" }],
},
{
code: `
function foo(): AsyncDisposable {}
const bar = foo()
`,
errors: [{ messageId: "missingAwaitUsing" }],
},
],
});
63 changes: 63 additions & 0 deletions eslint-local-rules/require-using-disposable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { ESLintUtils } from "@typescript-eslint/utils";
import ts from "typescript";
import * as utils from "ts-api-utils";

export const rule = ESLintUtils.RuleCreator.withoutDocs({
create(context) {
return {
VariableDeclaration(node) {
for (const declarator of node.declarations) {
if (!declarator.init) continue;
const services = ESLintUtils.getParserServices(context);
const type = services.getTypeAtLocation(declarator.init);
for (const typePart of parts(type)) {
if (!utils.isObjectType(typePart) || !typePart.symbol) {
continue;
}
if (
// bad check, but will do for now
// in the future, we should check for a `[Symbol.disposable]` property
// but I have no idea how to do that right now
typePart.symbol.escapedName === "Disposable" &&
node.kind != "using"
) {
context.report({
messageId: "missingUsing",
node: declarator,
});
}
if (
// similarly bad check
typePart.symbol.escapedName === "AsyncDisposable" &&
node.kind != "await using"
) {
context.report({
messageId: "missingAwaitUsing",
node: declarator,
});
}
}
}
},
};
},
meta: {
messages: {
missingUsing:
"Disposables should be allocated with `using <disposable>`.",
missingAwaitUsing:
"AsyncDisposables should be allocated with `await using <disposable>`.",
},
type: "suggestion",
schema: [],
},
defaultOptions: [],
});

function parts(type: ts.Type): ts.Type[] {
return type.isUnion()
? utils.unionTypeParts(type).flatMap(parts)
: type.isIntersection()
? utils.intersectionTypeParts(type).flatMap(parts)
: [type];
}
15 changes: 15 additions & 0 deletions eslint-local-rules/testSetup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { RuleTester } from "@typescript-eslint/rule-tester";
import nodeTest from "node:test";

RuleTester.it = nodeTest.it;
RuleTester.itOnly = nodeTest.only;
RuleTester.describe = nodeTest.describe;
RuleTester.afterAll = nodeTest.after;

export const ruleTester = new RuleTester({
parser: "@typescript-eslint/parser",
parserOptions: {
project: "./tsconfig.json",
tsconfigRootDir: __dirname + "/fixtures",
},
});
7 changes: 7 additions & 0 deletions eslint-local-rules/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "@tsconfig/node20/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"noEmit": true
}
}
Loading
Loading