Skip to content

Commit

Permalink
[#IP-86] tslint to eslint migration (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaeldisaro authored Apr 19, 2021
1 parent b587ce4 commit 20b5c2a
Show file tree
Hide file tree
Showing 13 changed files with 1,565 additions and 255 deletions.
22 changes: 22 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module.exports = {
env: {
browser: true,
es6: true,
node: true
},
ignorePatterns: [
"node_modules",
"generated",
"**/__tests__/*",
"**/__mocks__/*",
"Dangerfile.*",
"*.d.ts"
],
parser: "@typescript-eslint/parser",
parserOptions: {
project: "tsconfig.json",
sourceType: "module"
},
extends: ["@pagopa/eslint-config/strong"],
rules: {}
};
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@ coverage
.vscode
obj
bin

# eslint section
!.eslintrc.js
.eslintcache
2 changes: 1 addition & 1 deletion Dangerfile.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// import custom DangerJS rules
// see http://danger.systems/js
// see https://github.com/teamdigitale/danger-plugin-digitalcitizenship/
// tslint:disable-next-line:prettier
// eslint-disable-next-line prettier/prettier
import checkDangers from 'danger-plugin-digitalcitizenship';

checkDangers();
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@
Express adapter for Azure Functions.

Mostly a porting to TypeScript from [azure-function-express](https://github.com/yvele/azure-function-express).

You have to install a global dependency to run tests:
*npm i -g azure-functions-core-tools@3 --unsafe-perm true*
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@
"start": "npm-run-all --parallel start:host watch",
"pretest": "npm run build",
"test": "jest -i",
"lint": "tslint --project .",
"lint": "eslint . -c .eslintrc.js --ext .ts,.tsx",
"preversion": "auto-changelog --config .auto-changelog.json --unreleased --commit-limit false --stdout --template preview.hbs",
"version": "auto-changelog -p --config .auto-changelog.json --unreleased && git add CHANGELOG.md"
},
"devDependencies": {
"@azure/functions": "^1.0.3",
"@pagopa/eslint-config": "^1.3.1",
"@types/express": "^4.17.2",
"@types/express-serve-static-core": "^4.17.2",
"@types/jest": "^24.0.13",
Expand All @@ -32,15 +33,14 @@
"axios": "^0.19.0",
"danger": "^9.2.0",
"danger-plugin-digitalcitizenship": "*",
"eslint-plugin-prettier": "^3.3.1",
"express": "^4.17.1",
"italia-tslint-rules": "*",
"jest": "^24.8.0",
"npm-run-all": "^4.1.5",
"prettier": "^1.12.1",
"rimraf": "^2.6.2",
"tree-kill": "^1.2.2",
"ts-jest": "^24.0.2",
"tslint": "^5.1.0",
"typescript": "^3.5.0"
},
"peerDependencies": {
Expand Down
46 changes: 24 additions & 22 deletions src/ExpressAdapter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Context } from "@azure/functions";
import EventEmitter = require("events");
import { Context } from "@azure/functions";
import { Application } from "express";

import IncomingMessage from "./IncomingMessage";
Expand All @@ -10,7 +10,7 @@ import OutgoingMessage from "./OutgoingMessage";
* @throws {Error}
* @private
*/
// tslint:disable-next-line: no-any
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const isValidContext = (context: any): context is Context =>
context !== undefined &&
typeof context === "object" &&
Expand All @@ -21,7 +21,7 @@ const isValidContext = (context: any): context is Context =>
context.bindings.req.originalUrl &&
typeof context.bindings.req.originalUrl === "string";

export type RequestListener = (...args: readonly unknown[]) => void;
export type RequestListener = (...args: ReadonlyArray<unknown>) => void;

/**
* Express adapter allowing to handle Azure Function requests by wrapping in request events.
Expand All @@ -33,7 +33,7 @@ export default class ExpressAdapter extends EventEmitter {
/**
* @param {Object=} application Request listener (typically an express/connect instance)
*/
public constructor(application: Application) {
constructor(application: Application) {
super();

if (application !== undefined) {
Expand All @@ -53,25 +53,27 @@ export default class ExpressAdapter extends EventEmitter {
/**
* Create function ready to be exposed to Azure Function for request handling.
*/
public createAzureFunctionHandler = () => (context: Context) => {
if (!isValidContext(context)) {
return;
}
public createAzureFunctionHandler() {
return (context: Context): void => {
if (!isValidContext(context)) {
return;
}

const updateResponse = (
updater: (
prev: NonNullable<Context["res"]>
) => NonNullable<Context["res"]>
) => {
// tslint:disable-next-line: no-object-mutation
context.res = updater(context.res || {});
};
const updateResponse = (
updater: (
prev: NonNullable<Context["res"]>
) => NonNullable<Context["res"]>
): void => {
// eslint-disable-next-line functional/immutable-data
context.res = updater(context.res || {});
};

// 2. Wrapping
const req = new IncomingMessage(context);
const res = new OutgoingMessage(updateResponse, context.done);
// 2. Wrapping
const req = new IncomingMessage(context);
const res = new OutgoingMessage(updateResponse, context.done);

// 3. Synchronously calls each of the listeners registered for the event
this.emit("request", req, res);
};
// 3. Synchronously calls each of the listeners registered for the event
this.emit("request", req, res);
};
}
}
36 changes: 16 additions & 20 deletions src/IncomingMessage.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,32 @@
import { Context } from "@azure/functions";
import { Socket } from "net";
import { Readable } from "stream";
import { TLSSocket } from "tls";
import { Context } from "@azure/functions";

const NOOP = () => true;
const NOOP = (): true => true;

function removePortFromAddress(address: string): string {
return address ? address.replace(/:[0-9]*$/, "") : address;
}
const removePortFromAddress = (address: string): string =>
address ? address.replace(/:[0-9]*$/, "") : address;

/**
* Create a fake connection object
*
* @param {Object} context Raw Azure context object for a single HTTP request
* @returns {object} Connection object
*/
function createConnectionObject(
const createConnectionObject = (
context: Context
): Pick<Socket, "remoteAddress"> & Pick<TLSSocket, "encrypted"> {
): Pick<Socket, "remoteAddress"> & Pick<TLSSocket, "encrypted"> => {
const { req } = context.bindings;
const xForwardedFor = req.headers
? req.headers["x-forwarded-for"]
: undefined;

return {
encrypted:
req.originalUrl && req.originalUrl.toLowerCase().startsWith("https"),
encrypted: req.originalUrl?.toLowerCase().startsWith("https"),
remoteAddress: removePortFromAddress(xForwardedFor)
};
}
};

/**
* Copy useful context properties from the native context provided by the Azure
Expand All @@ -41,15 +39,13 @@ function createConnectionObject(
* @param {Object} context Raw Azure context object for a single HTTP request
* @returns {Object} Filtered context
*/
function sanitizeContext(context: Context): Context {
return {
...context,
// We don't want the developer to mess up express flow
// See https://github.com/yvele/azure-function-express/pull/12#issuecomment-336733540
done: NOOP,
log: context.log.bind(context)
};
}
const sanitizeContext = (context: Context): Context => ({
...context,
// We don't want the developer to mess up express flow
// See https://github.com/yvele/azure-function-express/pull/12#issuecomment-336733540
done: NOOP,
log: context.log.bind(context)
});

/**
* Request object wrapper
Expand Down Expand Up @@ -79,7 +75,7 @@ export default class IncomingMessage extends Readable {
context: sanitizeContext(context), // Specific to Azure Function
headers: req.headers || {}, // Should always have a headers object
socket: { destroy: NOOP },
// tslint:disable-next-line: no-any
// eslint-disable-next-line @typescript-eslint/no-explicit-any
url: (req as any).originalUrl
});
}
Expand Down
33 changes: 20 additions & 13 deletions src/OutgoingMessage.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// tslint:disable: variable-name
// eslint-disable camelcase

import { Context } from "@azure/functions";
import {
OutgoingHttpHeaders,
OutgoingMessage as NativeOutgoingMessage,
ServerResponse
} from "http";
import { Context } from "@azure/functions";

import { statusCodes } from "./statusCodes";

Expand All @@ -17,12 +17,13 @@ import { statusCodes } from "./statusCodes";
* @private
*/
export default class OutgoingMessage extends NativeOutgoingMessage {
public _hasBody = true;
public _headerNames = {};
public _headers = null;
public _removedHeader = {};
public readonly _hasBody = true;
public readonly _headerNames = {};
public readonly _headers = null;
public readonly _removedHeader = {};
// eslint-disable-next-line functional/prefer-readonly-type
public statusMessage!: string;
public statusCode!: number;
public readonly statusCode!: number;

/**
* Original implementation: https://github.com/nodejs/node/blob/v6.x/lib/_http_outgoing.js#L48
Expand All @@ -40,27 +41,29 @@ export default class OutgoingMessage extends NativeOutgoingMessage {

// Those methods cannot be prototyped because express explicitelly overrides __proto__
// See https://github.com/expressjs/express/blob/master/lib/middleware/init.js#L29
public end: NativeOutgoingMessage["end"] = (
// tslint:disable-next-line: no-any
chunkOrCb?: any
public readonly end: NativeOutgoingMessage["end"] = (
chunkOrCb: Parameters<NativeOutgoingMessage["end"]>[0]
) => {
// 1. Write head
// eslint-disable-next-line no-invalid-this
this.writeHead(this.statusCode); // Make jshttp/on-headers able to trigger

// 2. Return raw body to Azure Function runtime
// eslint-disable-next-line no-invalid-this
this.updateResponse(res => ({
...res,
body: chunkOrCb,
isRaw: true
}));
// eslint-disable-next-line no-invalid-this
this.done();
};

/**
* https://nodejs.org/api/http.html#http_response_writehead_statuscode_statusmessage_headers
* Original implementation: https://github.com/nodejs/node/blob/v6.x/lib/_http_server.js#L160
*/
public writeHead: ServerResponse["writeHead"] = (
public readonly writeHead: ServerResponse["writeHead"] = (
statusCode: number,
reasonOrHeaders?: string | OutgoingHttpHeaders,
headersOrUndefined?: OutgoingHttpHeaders
Expand All @@ -72,7 +75,7 @@ export default class OutgoingMessage extends NativeOutgoingMessage {
}

// 2. Status message
// tslint:disable-next-line: no-object-mutation
// eslint-disable-next-line functional/immutable-data, no-invalid-this
this.statusMessage =
typeof reasonOrHeaders === "string"
? reasonOrHeaders
Expand All @@ -85,24 +88,28 @@ export default class OutgoingMessage extends NativeOutgoingMessage {
? reasonOrHeaders
: headersOrUndefined;

// eslint-disable-next-line no-underscore-dangle, no-invalid-this
if (this._headers && headers !== undefined) {
// Slow-case: when progressive API and header fields are passed.
Object.keys(headers).forEach(k => {
const v = headers[k];
if (v) {
// eslint-disable-next-line no-invalid-this
this.setHeader(k, v);
}
});
}

// 4. Sets everything
// eslint-disable-next-line no-invalid-this
this.updateResponse(res => ({
...res,
// In order to uniformize node 6 behaviour with node 8 and 10,
// we want to never have undefined headers, but instead empty object
headers:
// eslint-disable-next-line no-underscore-dangle, no-invalid-this
this._headers && headers === undefined
? // tslint:disable-next-line: no-any
? // eslint-disable-next-line no-underscore-dangle, @typescript-eslint/no-explicit-any, no-invalid-this
(this as any)._renderHeaders()
: headers !== undefined
? headers
Expand Down
14 changes: 7 additions & 7 deletions src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,29 @@ import * as qs from "querystring";
// do not convert to default import despite vscode hints :-)
import * as treeKill from "tree-kill";

// tslint:disable-next-line: no-let
// eslint-disable-next-line functional/no-let
let spawnedFunc: ChildProcess;
// tslint:disable-next-line: no-let
// eslint-disable-next-line functional/no-let
let funcAddress: string;
// tslint:disable-next-line: no-let
// eslint-disable-next-line functional/no-let
let isStopping = false;

// do not reject promise on non-200 statuses
// tslint:disable-next-line: no-object-mutation
// eslint-disable-next-line functional/immutable-data
axios.defaults.validateStatus = () => true;

const startFunc = () =>
// tslint:disable-next-line: promise-must-complete
// eslint-disable-next-line @typescript-eslint/no-floating-promises
new Promise<{ p: ChildProcess; address: string }>(res => {
const func = spawn("func", ["start"]);
func.stdout.on("data", data => {
if (!isStopping) {
// tslint:disable-next-line: no-console
// eslint-disable-next-line no-console
console.log(`${data}`);
}
const matches = String(data).match(/(http:\/\/[^{]+)/);
if (matches && matches[1]) {
// tslint:disable-next-line: no-console
// eslint-disable-next-line no-console
console.log("serving function at %s", matches[1]);
res({
address: matches[1],
Expand Down
8 changes: 5 additions & 3 deletions src/createAzureFunctionsHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import ExpressAdapter from "./ExpressAdapter";
* @param {Object} requestListener Request listener (typically an express/connect instance)
* @returns {function(context: Object)} Azure Function handle
*/
export default function createAzureFunctionHandler(
const createAzureFunctionHandler = (
application: Application
): (context: Context) => void {
): ((context: Context) => void) => {
const adapter = new ExpressAdapter(application);
return adapter.createAzureFunctionHandler();
}
};

export default createAzureFunctionHandler;
2 changes: 1 addition & 1 deletion src/statusCodes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const statusCodes: { [key: number]: string | undefined } = {
export const statusCodes: { readonly [key: number]: string | undefined } = {
100: "Continue",
101: "Switching Protocols",
102: "Processing", // RFC 2518, obsoleted by RFC 4918
Expand Down
Loading

0 comments on commit 20b5c2a

Please sign in to comment.