Skip to content

Commit

Permalink
Merge pull request #5 from jfrconley/joco/fix-transformer-source-obli…
Browse files Browse the repository at this point in the history
…teration

Fix issues with source file transformation
  • Loading branch information
jfrconley authored Aug 9, 2023
2 parents 4999e30 + 8e988c3 commit 568ae5e
Show file tree
Hide file tree
Showing 24 changed files with 220 additions and 84 deletions.
5 changes: 5 additions & 0 deletions .changeset/giant-poems-relax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nornir/core": patch
---

add originator tag to nornir class
5 changes: 5 additions & 0 deletions .changeset/sweet-planets-return.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nornir/rest": patch
---

fix issues with source transformation
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ report.json
.tsbuildinfo
cdk.out
.env
vitest.config.js.timestamp-*
4 changes: 3 additions & 1 deletion dprint.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
"**/node_modules",
"**/*-lock.json",
"**/dist",
"**/bundle"
"**/bundle",
"*.d.ts",
"coverage-final.json"
],
"plugins": [
"https://plugins.dprint.dev/typescript-0.83.0.wasm",
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"jest-html-reporters": "^3.1.4",
"jest-junit": "^15.0.0",
"plop": "^3.1.2",
"scripts": "workspace:*",
"scripts": "workspace:^",
"syncpack": "^9.8.4",
"ts-patch": "^3.0.2",
"turbo": "^1.9.2",
Expand All @@ -42,6 +42,7 @@
"license": "MIT",
"packageManager": "[email protected]",
"private": true,
"repository": "nrfcloud/account-service",
"scripts": {
"clean": "rm -rf coverage reports node_modules/.cache/turbo && pnpm clean:turbo && pnpm turbo clean:single",
"clean:turbo": "rm -rf node_modules/.cache/turbo",
Expand Down
3 changes: 2 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"author": "John Conley",
"devDependencies": {
"@jest/globals": "^29.5.0",
"@nrfcloud/ts-json-schema-transformer": "^1.2.1",
"@nrfcloud/ts-json-schema-transformer": "^1.2.3",
"@types/jest": "^29.4.0",
"@types/node": "^18.15.11",
"esbuild": "^0.17.18",
Expand All @@ -27,6 +27,7 @@
"dist"
],
"license": "MIT",
"repository": "nrfcloud/account-service",
"scripts": {
"//10": "The following scripts are also available from the scripts package",
"//20": "You can also override them by specifying them here",
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/lib/nornir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ class NornirContext {

export type ResultMiddleware<Input, Output> = (input: Result<Input>, registry: AttachmentRegistry) => Output;

/**
* A Nornir is a chain of middleware that can be used to transform an input into an output.
*
* @originator nornir/core
*/
export class Nornir<Input, StepInput = Input> {
constructor(private readonly context: NornirContext = new NornirContext()) {}

Expand Down
4 changes: 2 additions & 2 deletions packages/rest/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"description": "A nornir library",
"version": "1.2.0",
"dependencies": {
"@nrfcloud/ts-json-schema-transformer": "^1.2.1",
"@nornir/core": "workspace",
"@nrfcloud/ts-json-schema-transformer": "^1.2.3",
"@types/aws-lambda": "^8.10.115",
"ajv": "^8.12.0",
"openapi-types": "^12.1.0",
Expand All @@ -14,7 +15,6 @@
},
"devDependencies": {
"@jest/globals": "^29.5.0",
"@nornir/core": "workspace:*",
"@types/jest": "^29.4.0",
"@types/node": "^18.15.11",
"eslint": "^8.45.0",
Expand Down
48 changes: 47 additions & 1 deletion packages/rest/src/runtime/decorators.mts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ import {Nornir} from "@nornir/core";
import {HttpRequest, HttpResponse} from "./http-event.mjs";
import {InstanceOf} from "ts-morph";

const UNTRANSFORMED_ERROR = new Error("@nornir/rest decorators have not been transformed. Have you setup ts-patch/ttypescript and added the transformer to your tsconfig.json?");
const UNTRANSFORMED_ERROR = new Error("nornir/rest decorators have not been transformed. Have you setup ts-patch/ttypescript and added the originator to your tsconfig.json?");

/**
* Use to mark a class as a REST controller
*
* @originator nornir/rest
*/
export function Controller<const Path extends string, const ApiId extends string>(_basePath: Path, _apiId?: ApiId) {
return <T extends { new(): unknown }>(_target: T, _ctx: ClassDecoratorContext): T => {
throw UNTRANSFORMED_ERROR;
Expand All @@ -14,34 +20,74 @@ const routeChainDecorator = <Input extends HttpRequest, Output extends HttpRespo
_propertyKey: ClassMethodDecoratorContext,
): never => {throw UNTRANSFORMED_ERROR};

/**
* Use to mark a method as a GET route
*
* @originator nornir/rest
*/
export function GetChain(_path: string) {
return routeChainDecorator;
}

/**
* Use to mark a method as a POST route
*
* @originator nornir/rest
*/
export function PostChain(_path: string) {
return routeChainDecorator;
}

/**
* Use to mark a method as a PUT route
*
* @originator nornir/rest
*/
export function PutChain(_path: string) {
return routeChainDecorator;
}

/**
* Use to mark a method as a PATCH route
*
* @originator nornir/rest
*/
export function PatchChain(_path: string) {
return routeChainDecorator;
}

/**
* Use to mark a method as a DELETE route
*
* @originator nornir/rest
*/
export function DeleteChain(_path: string) {
return routeChainDecorator;
}

/**
* Use to mark a method as a HEAD route
*
* @originator nornir/rest
*/
export function HeadChain(_path: string) {
return routeChainDecorator;
}

/**
* Use to mark a method as a OPTIONS route
*
* @originator nornir/rest
*/
export function OptionsChain(_path: string) {
return routeChainDecorator;
}

/**
* Use to mark a static method as an instance provider for a controller
*
* @originator nornir/rest
*/
export function Provider() {
return <T, K extends InstanceOf<T>>(_target: () => K, _propertyKey: ClassMethodDecoratorContext<T> & {static: true}): never => {
throw UNTRANSFORMED_ERROR
Expand Down
15 changes: 11 additions & 4 deletions packages/rest/src/transform/controller-meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,21 @@ export class ControllerMeta {
this.cache.clear();
}

public static create(project: Project, route: ts.ClassDeclaration, basePath: string, apiId: string): ControllerMeta {
public static create(
project: Project,
source: ts.SourceFile,
route: ts.ClassDeclaration,
basePath: string,
apiId: string,
): ControllerMeta {
const name = route.name;
if (!name) {
throw new Error("Route class must have a name");
}
if (this.cache.has(name)) {
throw new Error("Route already exists: " + name.getText());
}
const meta = new ControllerMeta(project, route, name, basePath, apiId);
const meta = new ControllerMeta(project, source, route, name, basePath, apiId);
this.cache.set(name, meta);
return meta;
}
Expand All @@ -58,6 +64,7 @@ export class ControllerMeta {

private constructor(
private readonly project: Project,
public readonly source: ts.SourceFile,
public readonly route: ts.ClassDeclaration,
public readonly identifier: ts.Identifier,
public readonly basePath: string,
Expand Down Expand Up @@ -95,7 +102,7 @@ export class ControllerMeta {

private generateRouteHolderCreateStatement() {
const nornirRestImport = FileTransformer.getOrCreateImport(
this.route.getSourceFile(),
this.source,
"@nornir/rest",
"RouteHolder",
);
Expand Down Expand Up @@ -134,7 +141,7 @@ export class ControllerMeta {
}

private generateRegisterRouteHolderStatement() {
const routerIdentifier = FileTransformer.getOrCreateImport(this.route.getSourceFile(), "@nornir/rest", "Router");
const routerIdentifier = FileTransformer.getOrCreateImport(this.source, "@nornir/rest", "Router");
const routerInstance = ts.factory.createCallExpression(
ts.factory.createPropertyAccessExpression(routerIdentifier, "get"),
[],
Expand Down
35 changes: 22 additions & 13 deletions packages/rest/src/transform/lib.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import path from "node:path";
import ts from "typescript";
import { StrictTransformationError } from "./error";
import { Project } from "./project";

const LIB_PATHS = [
path.join("node_modules", "@nornir/rest", "dist", "runtime"),
path.join("node_modules", "@nornir/core", "dist"),
path.join("packages", "rest", "dist", "runtime"),
path.join("packages", "core", "dist"),
];
export function isNornirNode(node: ts.Node) {
return hasOriginator(node, "nornir/core");
}

export function isNornirRestNode(node: ts.Node) {
return hasOriginator(node, "nornir/rest");
}

export function isNornirLib(file: string) {
return LIB_PATHS.some(libPath => file.includes(libPath));
export function hasOriginator(node: ts.Node, originator: string): boolean {
const jsdoc = ts.getJSDocTags(node);
return jsdoc.some((tag) => tag.tagName.getText() === "originator" && tag.comment === originator);
}

export function getStringLiteralOrConst(project: Project, node: ts.Expression): string | undefined {
Expand Down Expand Up @@ -39,14 +40,22 @@ export function separateNornirDecorators(
otherDecorators: ts.Decorator[];
nornirDecorators: NornirDecoratorInfo[];
} {
const nornirDecorators: { decorator: ts.Decorator; signature: ts.Signature; declaration: ts.Declaration }[] = [];
const nornirDecorators: {
decorator: ts.Decorator;
signature: ts.Signature;
declaration: ts.Declaration;
}[] = [];
const decorators: ts.Decorator[] = [];

for (const decorator of originalDecorators) {
const signature = project.checker.getResolvedSignature(decorator);
const fileName = signature?.declaration?.getSourceFile().fileName || "";
if (isNornirLib(fileName) && signature && signature.declaration) {
nornirDecorators.push({ decorator, signature, declaration: signature.declaration });
const parentDeclaration = signature?.getDeclaration()?.parent;
if (parentDeclaration && signature && signature.declaration && isNornirRestNode(parentDeclaration)) {
nornirDecorators.push({
decorator,
signature,
declaration: signature.declaration,
});
} else {
decorators.push(decorator);
}
Expand Down
9 changes: 7 additions & 2 deletions packages/rest/src/transform/transformers/class-transformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,18 @@ import { Project } from "../project";
import { ControllerProcessor } from "./processors/controller-processor";

export abstract class ClassTransformer {
public static transform(project: Project, node: ts.ClassDeclaration, context: ts.TransformationContext): ts.Node {
public static transform(
project: Project,
source: ts.SourceFile,
node: ts.ClassDeclaration,
context: ts.TransformationContext,
): ts.Node {
const originalDecorators = ts.getDecorators(node) || [];
if (!originalDecorators) return node;

const { nornirDecorators } = separateNornirDecorators(project, originalDecorators);
if (nornirDecorators.length === 0) return node;

return ControllerProcessor.process(project, node, nornirDecorators, context);
return ControllerProcessor.process(project, source, node, nornirDecorators, context);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ProviderProcessor } from "./processors/provider-processor";
export abstract class ControllerMethodTransformer {
private static transform(
project: Project,
source: ts.SourceFile,
node: ts.MethodDeclaration,
controller: ControllerMeta,
): ts.MethodDeclaration {
Expand All @@ -27,18 +28,19 @@ export abstract class ControllerMethodTransformer {

if (!method) return node;

return METHOD_DECORATOR_PROCESSORS[method](methodDecorator, project, node, controller);
return METHOD_DECORATOR_PROCESSORS[method](methodDecorator, project, source, node, controller);
}

public static transformControllerMethods(
project: Project,
source: ts.SourceFile,
node: ts.ClassDeclaration,
controller: ControllerMeta,
context: ts.TransformationContext,
) {
return ts.visitEachChild(node, (child) => {
if (ts.isMethodDeclaration(child)) {
return ControllerMethodTransformer.transform(project, child, controller);
return ControllerMethodTransformer.transform(project, source, child, controller);
}
return child;
}, context);
Expand All @@ -48,6 +50,7 @@ export abstract class ControllerMethodTransformer {
type Task = (
methodDecorator: NornirDecoratorInfo,
project: Project,
source: ts.SourceFile,
node: ts.MethodDeclaration,
controller: ControllerMeta,
) => ts.MethodDeclaration;
Expand Down
Loading

0 comments on commit 568ae5e

Please sign in to comment.