Skip to content
Merged
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
2 changes: 1 addition & 1 deletion compat-test/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ import migration from "express-zod-api/migration";

export default [
{ languageOptions: { parser }, plugins: { migration } },
{ files: ["**/*.ts"], rules: { "migration/v23": "error" } },
{ files: ["**/*.ts"], rules: { "migration/v24": "error" } },
];
3 changes: 2 additions & 1 deletion compat-test/migration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { readFile } from "node:fs/promises";
import { describe, test, expect } from "vitest";

describe("Migration", () => {
test("should fix the import", async () => {
// @todo update
test.skip("should fix the import", async () => {
const fixed = await readFile("./sample.ts", "utf-8");
expect(fixed).toBe("const test: HeaderSecurity = {};\n");
});
Expand Down
105 changes: 9 additions & 96 deletions express-zod-api/src/migration.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,16 @@
import {
ESLintUtils,
AST_NODE_TYPES as NT,
// AST_NODE_TYPES as NT,
type TSESLint,
type TSESTree,
// type TSESTree,
} from "@typescript-eslint/utils"; // eslint-disable-line allowed/dependencies -- special case

interface Queries {
headerSecurity: TSESTree.Identifier;
createConfig: TSESTree.ObjectExpression;
testMiddleware: TSESTree.ObjectExpression;
}
/*
interface Queries {}

type Listener = keyof Queries;

const queries: Record<Listener, string> = {
headerSecurity: `${NT.Identifier}[name='CustomHeaderSecurity']`,
createConfig: `${NT.CallExpression}[callee.name='createConfig'] > ${NT.ObjectExpression}`,
testMiddleware: `${NT.CallExpression}[callee.name='testMiddleware'] > ${NT.ObjectExpression}`,
};
const queries: Record<Listener, string> = {};

const listen = <
S extends { [K in Listener]: TSESLint.RuleFunction<Queries[K]> },
Expand All @@ -31,8 +24,9 @@ const listen = <
}),
{},
);
*/

const v23 = ESLintUtils.RuleCreator.withoutDocs({
const v24 = ESLintUtils.RuleCreator.withoutDocs({
meta: {
type: "problem",
fixable: "code",
Expand All @@ -44,88 +38,7 @@ const v23 = ESLintUtils.RuleCreator.withoutDocs({
},
},
defaultOptions: [],
create: (ctx) =>
listen({
headerSecurity: (node) =>
ctx.report({
node,
messageId: "change",
data: { subject: "interface", from: node.name, to: "HeaderSecurity" },
fix: (fixer) => fixer.replaceText(node, "HeaderSecurity"),
}),
createConfig: (node) => {
const wmProp = node.properties.find(
(prop) =>
prop.type === NT.Property &&
prop.key.type === NT.Identifier &&
prop.key.name === "wrongMethodBehavior",
);
if (wmProp) return;
ctx.report({
node,
messageId: "add",
data: {
subject: "wrongMethodBehavior property",
to: "configuration",
},
fix: (fixer) =>
fixer.insertTextAfterRange(
[node.range[0], node.range[0] + 1],
"wrongMethodBehavior: 404,",
),
});
},
testMiddleware: (node) => {
const ehProp = node.properties.find(
(
prop,
): prop is TSESTree.Property & {
value:
| TSESTree.ArrowFunctionExpression
| TSESTree.FunctionExpression;
} =>
prop.type === NT.Property &&
prop.key.type === NT.Identifier &&
prop.key.name === "errorHandler" &&
[NT.ArrowFunctionExpression, NT.FunctionExpression].includes(
prop.value.type,
),
);
if (!ehProp) return;
const hasComma = ctx.sourceCode.getTokenAfter(ehProp)?.value === ",";
const { body } = ehProp.value;
const configProp = node.properties.find(
(
prop,
): prop is TSESTree.Property & { value: TSESTree.ObjectExpression } =>
prop.type === NT.Property &&
prop.key.type === NT.Identifier &&
prop.key.name === "configProps" &&
prop.value.type === NT.ObjectExpression,
);
const replacement = `errorHandler: new ResultHandler({ positive: [], negative: [], handler: ({ error, response }) => {${ctx.sourceCode.getText(body)}} }),`;
ctx.report({
node: ehProp,
messageId: "move",
data: { subject: "errorHandler", to: "configProps" },
fix: (fixer) => [
fixer.removeRange([
ehProp.range[0],
ehProp.range[1] + (hasComma ? 1 : 0),
]),
configProp
? fixer.insertTextAfterRange(
[configProp.value.range[0], configProp.value.range[0] + 1],
replacement,
)
: fixer.insertTextAfterRange(
[node.range[0], node.range[0] + 1],
`configProps: {${replacement}},`,
),
],
});
},
}),
create: () => ({}),
});

/**
Expand All @@ -141,5 +54,5 @@ const v23 = ESLintUtils.RuleCreator.withoutDocs({
* ];
* */
export default {
rules: { v23 },
rules: { v24 },
} satisfies TSESLint.Linter.Plugin;
2 changes: 1 addition & 1 deletion express-zod-api/tests/__snapshots__/migration.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
exports[`Migration > should consist of one rule being the major version of the package 1`] = `
{
"rules": {
"v23": {
"v24": {
"create": [Function],
"defaultOptions": [],
"meta": {
Expand Down
69 changes: 11 additions & 58 deletions express-zod-api/tests/migration.spec.ts
Original file line number Diff line number Diff line change
@@ -1,76 +1,29 @@
import { RuleTester } from "@typescript-eslint/rule-tester";
import migration from "../src/migration";
import parser from "@typescript-eslint/parser";
import { version } from "../package.json";
// import parser from "@typescript-eslint/parser";
// import { version } from "../package.json";

RuleTester.afterAll = afterAll;
RuleTester.describe = describe;
RuleTester.it = it;

/*
const tester = new RuleTester({
languageOptions: { parser },
});
*/

describe("Migration", () => {
test("should consist of one rule being the major version of the package", () => {
expect(migration.rules).toHaveProperty(`v${version.split(".")[0]}`);
// @todo restore
// expect(migration.rules).toHaveProperty(`v${version.split(".")[0]}`);
expect(migration).toMatchSnapshot();
});

tester.run("v23", migration.rules.v23, {
valid: [
`import { HeaderSecurity } from "express-zod-api";`,
`createConfig({ wrongMethodBehavior: 405 });`,
`testMiddleware({ middleware })`,
],
invalid: [
{
code: `const security: CustomHeaderSecurity = {};`,
output: `const security: HeaderSecurity = {};`,
errors: [
{
messageId: "change",
data: {
subject: "interface",
from: "CustomHeaderSecurity",
to: "HeaderSecurity",
},
},
],
},
{
code: `createConfig({});`,
output: `createConfig({wrongMethodBehavior: 404,});`,
errors: [
{
messageId: "add",
data: {
subject: "wrongMethodBehavior property",
to: "configuration",
},
},
],
},
{
code: `testMiddleware({ errorHandler: (error, response) => response.end(error.message) })`,
output: `testMiddleware({configProps: {errorHandler: new ResultHandler({ positive: [], negative: [], handler: ({ error, response }) => {response.end(error.message)} }),}, })`,
errors: [
{
messageId: "move",
data: { subject: "errorHandler", to: "configProps" },
},
],
},
{
code: `testMiddleware({ errorHandler(error, response) { response.end(error.message) }, configProps: { wrongMethodBehavior: 404 } })`,
output: `testMiddleware({ configProps: {errorHandler: new ResultHandler({ positive: [], negative: [], handler: ({ error, response }) => {{ response.end(error.message) }} }), wrongMethodBehavior: 404 } })`,
errors: [
{
messageId: "move",
data: { subject: "errorHandler", to: "configProps" },
},
],
},
],
/*
tester.run("v24", migration.rules.v24, {
valid: [],
invalid: [],
});
*/
});