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

Utility class to detect Typescript functions that can possibly be used as Lambda Function Handlers. #142

Merged
merged 8 commits into from
Oct 29, 2018
3,413 changes: 1,710 additions & 1,703 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,6 @@
"tslint": "^5.11.0",
"tslint-eslint-rules": "^5.4.0",
"tslint-no-circular-imports": "^0.6.1",
"typescript": "^3.0.1",
"vsce": "^1.51.1",
"vscode": "^1.1.21",
"vscode-nls-dev": "^3.2.2"
Expand All @@ -223,6 +222,7 @@
"@types/handlebars": "^4.0.39",
"@types/js-yaml": "^3.11.2",
"@types/opn": "^5.1.0",
"@types/typescript": "^2.0.0",
mpiroc marked this conversation as resolved.
Show resolved Hide resolved
"async-lock": "^1.1.3",
"aws-sdk": "^2.317.0",
"cloudformation-schema-js-yaml": "^1.0.1",
Expand All @@ -233,6 +233,7 @@
"npm": "^6.1.0",
"opn": "^5.4.0",
"request": "^2.88.0",
"typescript": "^3.1.3",
"vscode-nls": "^3.2.4",
"vue": "^2.5.16",
"xml2js": "^0.4.19"
Expand Down
23 changes: 23 additions & 0 deletions src/shared/lambdaHandlerSearch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*!
* Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'

export interface LambdaHandlerCandidate {
handlerName: string,
filename: string,
// Represents all of the component names that are used in a handler name (eg: filename, assembly, class, function)
handlerStack: string[],
mpiroc marked this conversation as resolved.
Show resolved Hide resolved
}

export interface LambdaHandlerSearch {

/**
* @description Looks for functions that appear to be valid Lambda Function Handlers.
* @returns A collection of information for each detected candidate.
*/
findCandidateLambdaHandlers(): Promise<LambdaHandlerCandidate[]>

}
116 changes: 116 additions & 0 deletions src/shared/typescriptLambdaHandlerSearch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*!
* Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'

import * as path from 'path'
import * as ts from 'typescript'
import * as vscode from 'vscode'
import * as filesystem from './filesystem'
import { LambdaHandlerCandidate, LambdaHandlerSearch } from './lambdaHandlerSearch'

/**
* Detects functions that could possibly be used as Lambda Function Handlers from a Typescript file.
*/
export class TypescriptLambdaHandlerSearch implements LambdaHandlerSearch {

private _uri!: vscode.Uri

public constructor(uri: vscode.Uri) {
this._uri = uri
}

/**
* @description Looks for functions that appear to be valid Lambda Function Handlers.
* @returns A collection of information for each detected candidate.
*/
public async findCandidateLambdaHandlers(): Promise<LambdaHandlerCandidate[]> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • What's the performance on very large (i.e. 10,000+ lines, many functions) files?
  • Will this be called from the UI thread?
  • Is it feasible to only parse code that's currently in the user's viewport (i.e., not scrolled out of sight)?
  • Is it feasible to return a Promise<LambdaHandlerCandidate | undefined>[] (or a Promise<Promise<LambdaHandlerCandidate | undefined>[]>) instead? It would be awesome if we could update the view for each function independently.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I've seen of CodeLens, extensions are given the file as a whole, not for a viewport.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've put the performance investigation into #145

return await this.getCandidateHandlers(this._uri.fsPath)
}

private async getCandidateHandlers(filename: string): Promise<LambdaHandlerCandidate[]> {
const fileContents = await filesystem.readFileAsyncAsString(filename)

const sourceFile = ts.createSourceFile(filename, fileContents, ts.ScriptTarget.Latest, true)
mpiroc marked this conversation as resolved.
Show resolved Hide resolved

const handlers: LambdaHandlerCandidate[] = []

sourceFile.forEachChild(
childNode => {
const foundHandlers = TypescriptLambdaHandlerSearch.visitSourceFileChild(
childNode,
filename
)
handlers.push(...foundHandlers)
}
)

return handlers
}

/**
* @description looks for Lambda Handler candidates in given node.
* Lambda Handler candidates are top level exported methods/functions.
*
* @param node SourceFile child node to visit
* @param filename filename of the loaded SourceFile
* @returns Collection of candidate Lambda handler information, empty array otherwise
*/
private static visitSourceFileChild(node: ts.Node, filename: string): LambdaHandlerCandidate[] {
const handlers: LambdaHandlerCandidate[] = []

if (this.isNodeExported(node)) {
const baseFilename = path.parse(filename).name

if (ts.isMethodDeclaration(node)) {
if (this.isMethodLambdaHandlerCandidate(node)) {
const handlerStack = [baseFilename, node.name!.getText()]
mpiroc marked this conversation as resolved.
Show resolved Hide resolved
handlers.push({
filename: filename,
handlerName: handlerStack.join('.'),
handlerStack: handlerStack
})
}
} else if (ts.isFunctionDeclaration(node)) {
if (this.isFunctionLambdaHandlerCandidate(node)) {
const handlerStack = [baseFilename, node.name!.getText()]
mpiroc marked this conversation as resolved.
Show resolved Hide resolved
handlers.push({
filename: filename,
handlerName: handlerStack.join('.'),
handlerStack: handlerStack
})
}
}
}

return handlers
mpiroc marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* @description Indicates whether or not a node is marked as visible outside this file
* @param node Node to check
* @returns true if node is exported, false otherwise
*/
private static isNodeExported(node: ts.Node): boolean {
// tslint:disable-next-line:no-bitwise
return ((ts.getCombinedModifierFlags(node as ts.Declaration) & ts.ModifierFlags.Export) !== 0)
mpiroc marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* @description Indicates whether or not a method could be a Lambda Handler
* @param node Method Node to check
*/
private static isMethodLambdaHandlerCandidate(node: ts.MethodDeclaration): boolean {
mpiroc marked this conversation as resolved.
Show resolved Hide resolved
return node.parameters.length <= 3
}

/**
* @description Indicates whether or not a function could be a Lambda Handler
* @param node Function Node to check
*/
private static isFunctionLambdaHandlerCandidate(node: ts.FunctionDeclaration): boolean {
return node.parameters.length <= 3 && !!node.name
mpiroc marked this conversation as resolved.
Show resolved Hide resolved
}
}
24 changes: 24 additions & 0 deletions src/test/samples/javascript/sampleClasses.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*!
* Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'

class NonExportedClass {
publicMethod() { }
mpiroc marked this conversation as resolved.
Show resolved Hide resolved
privateMethod() { }
}

class ExportedClass {
publicMethod() { }
privateMethod() { }

static publicStaticMethod() { }
}
exports.ExportedClass = ExportedClass

function functionWithNoArgs() { }

function exportedFunctionWithNoArgs() { }
exports.exportedFunctionWithNoArgs = exportedFunctionWithNoArgs
31 changes: 31 additions & 0 deletions src/test/samples/javascript/sampleFunctions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*!
* Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'

function functionWithNoArgs() { }

export function exportedFunctionWithNoArgs() { }
exports.exportedFunctionWithNoArgs = exportedFunctionWithNoArgs

function functionWithOneArg(arg1) { }

export function exportedFunctionWithOneArg(arg1) { }
exports.exportedFunctionWithOneArg = exportedFunctionWithOneArg

function functionWithTwoArgs(arg1, arg2) { }

export function exportedFunctionWithTwoArgs(arg1, arg2) { }
exports.exportedFunctionWithTwoArgs = exportedFunctionWithTwoArgs

function functionWithThreeArgs(arg1, arg2, arg3) { }

export function exportedFunctionWithThreeArgs(arg1, arg2, arg3) { }
exports.exportedFunctionWithThreeArgs = exportedFunctionWithThreeArgs

function functionWithFourArgs(arg1, arg2, arg3, arg4) { }

export function exportedFunctionWithFourArgs(arg1, arg2, arg3, arg4) { }
exports.exportedFunctionWithFourArgs = exportedFunctionWithFourArgs
28 changes: 28 additions & 0 deletions src/test/samples/typescript/sampleClasses.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*!
* Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'

/* tslint:disable:no-unused-variable */

// @ts-ignore
class NonExportedClass {
public publicMethod(): void { }
// @ts-ignore
private privateMethod(): void { }
}

export class ExportedClass {
public publicMethod(): void { }
// @ts-ignore
private privateMethod(): void { }

public static publicStaticMethod(): void { }
}

// @ts-ignore
function functionWithNoArgs(): void { }

export function exportedFunctionWithNoArgs(): void { }
33 changes: 33 additions & 0 deletions src/test/samples/typescript/sampleFunctions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*!
* Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'

/* tslint:disable:no-unused-variable */

// @ts-ignore
function functionWithNoArgs(): void { }

export function exportedFunctionWithNoArgs(): void { }

// @ts-ignore
function functionWithOneArg(arg1: string): void { }

export function exportedFunctionWithOneArg(arg1: string): void { }

// @ts-ignore
function functionWithTwoArgs(arg1: string, arg2: string): void { }

export function exportedFunctionWithTwoArgs(arg1: string, arg2: string): void { }

// @ts-ignore
function functionWithThreeArgs(arg1: string, arg2: string, arg3: string): void { }

export function exportedFunctionWithThreeArgs(arg1: string, arg2: string, arg3: string): void { }

// @ts-ignore
function functionWithFourArgs(arg1: string, arg2: string, arg3: string, arg4: string): void { }

export function exportedFunctionWithFourArgs(arg1: string, arg2: string, arg3: string, arg4: string): void { }
22 changes: 22 additions & 0 deletions src/test/samples/typescript/sampleInterfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*!
* Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

'use strict'

/* tslint:disable:no-unused-variable */

// @ts-ignore
interface NonExportedInterface {
method(): void
}

export interface ExportedInterface {
method(): void
}

// @ts-ignore
function functionWithNoArgs(): void { }

export function exportedFunctionWithNoArgs(): void { }
Loading