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

[#IP-86] tslint to eslint migration #16

Merged
merged 8 commits into from
Apr 19, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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]
michaeldisaro marked this conversation as resolved.
Show resolved Hide resolved
) => {
// 1. Write head
// eslint-disable-next-line no-invalid-this
michaeldisaro marked this conversation as resolved.
Show resolved Hide resolved
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()
michaeldisaro marked this conversation as resolved.
Show resolved Hide resolved
: 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