Skip to content

Commit

Permalink
Cleaning up migration for v21 (#2087)
Browse files Browse the repository at this point in the history
  • Loading branch information
RobinTail authored Oct 10, 2024
1 parent 6c8571e commit f200136
Show file tree
Hide file tree
Showing 5 changed files with 14 additions and 294 deletions.
153 changes: 5 additions & 148 deletions src/migration.ts
Original file line number Diff line number Diff line change
@@ -1,157 +1,14 @@
import { ESLintUtils, type TSESLint } from "@typescript-eslint/utils";
import { name as importName } from "../package.json";

const testerName = "testEndpoint";

const changedMethods = {
createLogger: "BuiltinLogger",
createResultHandler: "ResultHandler",
createMiddleware: "Middleware",
};

const changedProps = {
getPositiveResponse: "positive",
getNegativeResponse: "negative",
responseProps: "responseOptions",
middleware: "handler",
};

const removedProps = { fnMethod: null };

const shouldAct = <T extends Record<string, unknown>>(
subject: unknown,
scope: T,
): subject is keyof T => typeof subject === "string" && subject in scope;

const v20 = ESLintUtils.RuleCreator.withoutDocs({
const v21 = ESLintUtils.RuleCreator.withoutDocs({
meta: {
type: "problem",
fixable: "code",
schema: [],
messages: {
change: "Change {{subject}} {{from}} to {{to}}.",
remove: "Remove {{subject}} {{name}}.",
},
messages: {},
},
defaultOptions: [],
create: (ctx) => ({
ImportDeclaration: (node) => {
if (node.source.value === importName) {
for (const spec of node.specifiers) {
if (
spec.type === "ImportSpecifier" &&
shouldAct(spec.imported.name, changedMethods)
) {
const replacement = changedMethods[spec.imported.name];
ctx.report({
node: spec.imported,
messageId: "change",
data: {
subject: "import",
from: spec.imported.name,
to: replacement,
},
fix: (fixer) => fixer.replaceText(spec, replacement),
});
}
}
}
},
CallExpression: (node) => {
if (
node.callee.type === "Identifier" &&
shouldAct(node.callee.name, changedMethods)
) {
const replacement = `new ${changedMethods[node.callee.name]}`;
ctx.report({
node: node.callee,
messageId: "change",
data: { subject: "call", from: node.callee.name, to: replacement },
fix: (fixer) => fixer.replaceText(node.callee, replacement),
});
}
if (
node.callee.type === "Identifier" &&
node.callee.name === testerName &&
node.arguments.length === 1 &&
node.arguments[0].type === "ObjectExpression"
) {
for (const prop of node.arguments[0].properties) {
if (prop.type === "Property" && prop.key.type === "Identifier") {
if (shouldAct(prop.key.name, changedProps)) {
const replacement = changedProps[prop.key.name];
ctx.report({
node: prop,
messageId: "change",
data: {
subject: "property",
from: prop.key.name,
to: replacement,
},
fix: (fixer) => fixer.replaceText(prop.key, replacement),
});
}
if (shouldAct(prop.key.name, removedProps)) {
ctx.report({
node: prop,
messageId: "remove",
data: { subject: "property", name: prop.key.name },
fix: (fixer) =>
ctx.sourceCode.getTokenAfter(prop)?.value === "," &&
prop.range
? fixer.removeRange([prop.range[0], prop.range[1] + 1])
: fixer.remove(prop),
});
}
}
}
}
},
NewExpression: (node) => {
if (
node.callee.type === "Identifier" &&
[
changedMethods.createResultHandler,
changedMethods.createMiddleware,
].includes(node.callee.name) &&
node.arguments.length === 1 &&
node.arguments[0].type === "ObjectExpression"
) {
for (const prop of node.arguments[0].properties) {
if (
prop.type === "Property" &&
prop.key.type === "Identifier" &&
shouldAct(prop.key.name, changedProps)
) {
const replacement = changedProps[prop.key.name];
ctx.report({
node: prop,
messageId: "change",
data: {
subject: "property",
from: prop.key.name,
to: replacement,
},
fix: (fixer) => fixer.replaceText(prop.key, replacement),
});
}
}
}
},
Identifier: (node) => {
if (
node.name === "MockOverrides" &&
node.parent.type === "TSInterfaceDeclaration"
) {
ctx.report({
node,
messageId: "remove",
data: { subject: "augmentation", name: node.name },
fix: (fixer) => fixer.remove(node.parent),
});
}
},
}),
create: () => ({}),
});

/**
Expand All @@ -163,9 +20,9 @@ const v20 = ESLintUtils.RuleCreator.withoutDocs({
* import migration from "express-zod-api/migration";
* export default [
* { languageOptions: {parser}, plugins: {migration} },
* { files: ["**\/*.ts"], rules: { "migration/v20": "error" } }
* { files: ["**\/*.ts"], rules: { "migration/v21": "error" } }
* ];
* */
export default {
rules: { v20 },
rules: { v21 },
} satisfies TSESLint.Linter.Plugin;
2 changes: 1 addition & 1 deletion tests/compat/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/v20": "error" } },
{ files: ["**/*.ts"], rules: { "migration/v21": "error" } },
];
3 changes: 2 additions & 1 deletion tests/compat/migration.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { readFile } from "node:fs/promises";

/** @todo update the pretest and assertion with actual code for v21 */
describe("Migration", () => {
test("should fix the import", async () => {
const fixed = await readFile("./sample.ts", "utf-8");
expect(fixed).toMatch(/BuiltinLogger/);
expect(fixed).toMatch(/express-zod-api/);
});
});
2 changes: 1 addition & 1 deletion tests/compat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"typescript": "~5.1.0"
},
"scripts": {
"pretest": "echo 'import { createLogger } from \"express-zod-api\";' > sample.ts",
"pretest": "echo 'import {} from \"express-zod-api\";' > sample.ts",
"test": "eslint --fix && vitest --run && rm sample.ts"
}
}
148 changes: 5 additions & 143 deletions tests/unit/migration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,152 +12,14 @@ const tester = new RuleTester({
});

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

tester.run("v20", migration.rules.v20, {
valid: [
{ code: `import { BuiltinLogger } from "express-zod-api"` },
{ code: `import { ResultHandler } from "express-zod-api"` },
{ code: `import { Middleware } from "express-zod-api"` },
{ code: `new BuiltinLogger({})` },
{ code: `new ResultHandler({ positive: {}, negative: {} })` },
{ code: `new Middleware({ handler: {} })` },
{ code: `testEndpoint({})` },
],
invalid: [
{
code: `import { createLogger } from "express-zod-api"`,
output: `import { BuiltinLogger } from "express-zod-api"`,
errors: [
{
messageId: "change",
data: {
subject: "import",
from: "createLogger",
to: "BuiltinLogger",
},
},
],
},
{
code: `import { createResultHandler } from "express-zod-api"`,
output: `import { ResultHandler } from "express-zod-api"`,
errors: [
{
messageId: "change",
data: {
subject: "import",
from: "createResultHandler",
to: "ResultHandler",
},
},
],
},
{
code: `import { createMiddleware } from "express-zod-api"`,
output: `import { Middleware } from "express-zod-api"`,
errors: [
{
messageId: "change",
data: {
subject: "import",
from: "createMiddleware",
to: "Middleware",
},
},
],
},
{
code: `createLogger({})`,
output: `new BuiltinLogger({})`,
errors: [
{
messageId: "change",
data: {
subject: "call",
from: "createLogger",
to: "new BuiltinLogger",
},
},
],
},
{
code: `createResultHandler({})`,
output: `new ResultHandler({})`,
errors: [
{
messageId: "change",
data: {
subject: "call",
from: "createResultHandler",
to: "new ResultHandler",
},
},
],
},
{
code: `new ResultHandler({ getPositiveResponse: {}, getNegativeResponse: {} })`,
output: `new ResultHandler({ positive: {}, negative: {} })`,
errors: [
{
messageId: "change",
data: {
subject: "property",
from: "getPositiveResponse",
to: "positive",
},
},
{
messageId: "change",
data: {
subject: "property",
from: "getNegativeResponse",
to: "negative",
},
},
],
},
{
code: `new Middleware({ middleware: {} })`,
output: `new Middleware({ handler: {} })`,
errors: [
{
messageId: "change",
data: { subject: "property", from: "middleware", to: "handler" },
},
],
},
{
code: `testEndpoint({ fnMethod: {}, responseProps: {} })`,
output: `testEndpoint({ responseOptions: {} })`,
errors: [
{
messageId: "remove",
data: { subject: "property", name: "fnMethod" },
},
{
messageId: "change",
data: {
subject: "property",
from: "responseProps",
to: "responseOptions",
},
},
],
},
{
code: `interface MockOverrides extends Mock {}`,
output: ``,
errors: [
{
messageId: "remove",
data: { subject: "augmentation", name: "MockOverrides" },
},
],
},
],
tester.run("v21", migration.rules.v21, {
valid: [{ code: `import {} from "express-zod-api"` }],
invalid: [],
});

0 comments on commit f200136

Please sign in to comment.