Skip to content

Commit

Permalink
version 1.2
Browse files Browse the repository at this point in the history
  • Loading branch information
scale-tone committed Apr 9, 2023
1 parent 5c153ba commit 9a4bf8b
Show file tree
Hide file tree
Showing 21 changed files with 1,159 additions and 780 deletions.
2 changes: 1 addition & 1 deletion cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "az-func-as-a-graph-az-do-build-task",
"version": "1.2.1",
"version": "1.2.2",
"description": "Azure DevOps build/release task to generate interactive code diagrams for your Azure Functions",
"main": "index.js",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion cli/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"version": {
"Major": 1,
"Minor": 2,
"Patch": 1
"Patch": 2
},
"instanceNameFormat": "az-func-as-a-graph",
"inputs": [
Expand Down
2 changes: 1 addition & 1 deletion vscode-web-ext-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"@material-ui/core": "^4.12.4",
"@material-ui/icons": "^4.11.3",
"axios": "^0.27.2",
"az-func-as-a-graph.core": "^1.0.0",
"az-func-as-a-graph.core": "^1.1.0",
"mermaid": "9.1.3",
"mobx": "^5.15.7",
"mobx-react": "^6.3.1",
Expand Down
4 changes: 4 additions & 0 deletions vscode-web-ext/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Change Log

## Version 1.2

- Support for [PowerShell](https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-powershell?tabs=portal) and [Python V2](https://techcommunity.microsoft.com/t5/azure-compute-blog/azure-functions-v2-python-programming-model/ba-p/3665168).

## Version 1.1

- UI improvements.
Expand Down
2 changes: 1 addition & 1 deletion vscode-web-ext/dist/web/extension.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion vscode-web-ext/dist/web/extension.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion vscode-web-ext/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "az-func-as-a-graph",
"displayName": "az-func-as-a-graph",
"description": "Visualizes your Azure Functions project in form of an interactive graph",
"version": "1.1.0",
"version": "1.2.0",
"engines": {
"vscode": "^1.75.0"
},
Expand Down
6 changes: 3 additions & 3 deletions vscode-web-ext/src/web/FileSystemWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export class FileSystemWrapper extends FileSystemWrapperBase {
return path1.substring(0, i);
}

protected async readFile(path: string): Promise<string> {
public async readFile(path: string): Promise<string> {

const uri = vscode.Uri.parse(path);

Expand All @@ -29,7 +29,7 @@ export class FileSystemWrapper extends FileSystemWrapperBase {
return new TextDecoder().decode(bytes);
}

protected async isDirectory(path: string): Promise<boolean> {
public async isDirectory(path: string): Promise<boolean> {

const uri = vscode.Uri.parse(path);

Expand All @@ -38,7 +38,7 @@ export class FileSystemWrapper extends FileSystemWrapperBase {
return stat.type === vscode.FileType.Directory;
}

protected async readDir(path: string): Promise<string[]> {
public async readDir(path: string): Promise<string[]> {

const uri = vscode.Uri.parse(path);

Expand Down
85 changes: 85 additions & 0 deletions vscode-web-ext/src/web/core/cSharpFunctionProjectParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { FunctionsMap } from "./FunctionsMap";
import { getCodeInBrackets, posToLineNr, removeNamespace } from "./traverseFunctionProjectUtils";

import { FunctionProjectCodeParser } from "./functionProjectCodeParser";

export class CSharpFunctionProjectParser extends FunctionProjectCodeParser {

protected async getFunctionsAndTheirCodesAsync(functionNames: string[], hostJsonFolder: string): Promise<{ name: string; code: string; filePath: string; pos: number; lineNr: number; }[]> {

const promises = functionNames.map(async name => {

const match = await this._fileSystemWrapper.findFileRecursivelyAsync(hostJsonFolder, '.+\\.cs$', true, this.getFunctionStartRegex(name));

if (!match) {
return undefined;
}

const pos = !match.pos ? 0 : match.pos;
const lineNr = posToLineNr(match.code, pos);
const code = getCodeInBrackets(match.code!, match.pos! + match.length!, '{', '}', '\n').code;

return { name, code, filePath: match.filePath, pos, lineNr };
});

return (await Promise.all(promises)).filter(f => !!f) as any;
}

protected async traverseProjectCode(projectFolder: string): Promise<FunctionsMap> {

const result: any = {};

const fileNameRegex = new RegExp('.+\\.cs$', 'i');

for await (const func of this._fileSystemWrapper.findFunctionsRecursivelyAsync(projectFolder, fileNameRegex, this.getFunctionAttributeRegex())) {

const bindings = this.tryExtractBindings(func.declarationCode);

if ( !(
bindings.some(b => b.type === 'orchestrationTrigger') ||
bindings.some(b => b.type === 'entityTrigger') ||
bindings.some(b => b.type === 'activityTrigger')
)) {

// Also trying to extract multiple output bindings
bindings.push(...await this.extractOutputBindings(projectFolder, func.declarationCode, fileNameRegex));
}

result[func.functionName] = {

filePath: func.filePath,
pos: func.pos,
lineNr: func.lineNr,

bindings: [...bindings]
};
}

return result;
}

private async extractOutputBindings(projectFolder: string, functionCode: string, fileNameRegex: RegExp): Promise<{ type: string, direction: string }[]> {

const returnTypeMatch = this.functionReturnTypeRegex.exec(functionCode);
if (!returnTypeMatch) {
return [];
}

const returnTypeName = removeNamespace(returnTypeMatch[3]);
if (!returnTypeName) {
return [];
}

const returnTypeDefinition = await this._fileSystemWrapper.findFileRecursivelyAsync(projectFolder, fileNameRegex, true, this.getClassDefinitionRegex(returnTypeName));
if (!returnTypeDefinition) {
return [];
}

const classBody = getCodeInBrackets(returnTypeDefinition.code!, (returnTypeDefinition.pos ?? 0) + (returnTypeDefinition.length ?? 0), '{', '}');
if (!classBody.code) {
return [];
}

return this.tryExtractBindings(classBody.code);
}
}
56 changes: 56 additions & 0 deletions vscode-web-ext/src/web/core/fSharpFunctionProjectParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { FunctionsMap } from "./FunctionsMap";
import { getCodeInBrackets, posToLineNr } from "./traverseFunctionProjectUtils";

import { RegExAndPos } from "./fileSystemWrapperBase";
import { FunctionProjectCodeParser } from "./functionProjectCodeParser";

export class FSharpFunctionProjectParser extends FunctionProjectCodeParser {

protected async getFunctionsAndTheirCodesAsync(functionNames: string[], hostJsonFolder: string): Promise<{ name: string; code: string; filePath: string; pos: number; lineNr: number; }[]> {

const promises = functionNames.map(async name => {

const match = await this._fileSystemWrapper.findFileRecursivelyAsync(hostJsonFolder, '.+\\.fs$', true, this.getFunctionStartRegex(name));

if (!match) {
return undefined;
}

const code = getCodeInBrackets(match.code!, match.pos! + match.length!, '{', '}', '\n').code;
const pos = !match.pos ? 0 : match.pos;
const lineNr = posToLineNr(match.code, pos);

return { name, code, filePath: match.filePath, pos, lineNr };
});

return (await Promise.all(promises)).filter(f => !!f) as any;
}

protected async traverseProjectCode(projectFolder: string): Promise<FunctionsMap> {

const result: any = {};

for await (const func of this._fileSystemWrapper.findFunctionsRecursivelyAsync(projectFolder, new RegExp('.+\\.fs$', 'i'), this.getFunctionAttributeRegex())) {

const bindings = this.tryExtractBindings(func.declarationCode);

result[func.functionName] = {

filePath: func.filePath,
pos: func.pos,
lineNr: func.lineNr,

bindings: [...bindings]
};
}

return result;
}

protected getFunctionAttributeRegex(): RegExAndPos {
return {
regex: new RegExp(`\\[<\\s*Function(Name)?\\s*\\((["\\w\\s\\.\\(\\)-]+)\\)`, 'g'),
pos: 2
};
}
}
37 changes: 37 additions & 0 deletions vscode-web-ext/src/web/core/fileSystemWrapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import * as fileSystem from 'fs';
import * as path from 'path';
import { FileSystemWrapperBase } from './fileSystemWrapperBase';

// Implements common filesystem routines via 'fs' module
export class FileSystemWrapper extends FileSystemWrapperBase {

public joinPath(path1: string, path2: string): string {

return path.join(path1, path2);
}

public dirName(path1: string): string {

return path.dirname(path1);
}

public async readFile(path: string): Promise<string> {

return await fileSystem.promises.readFile(path, { encoding: 'utf8' });
}

public async isDirectory(path: string): Promise<boolean> {

return (await fileSystem.promises.lstat(path)).isDirectory();
}

public async readDir(path: string): Promise<string[]> {

return await fileSystem.promises.readdir(path);
}

public async pathExists(path: string): Promise<boolean> {

return fileSystem.existsSync(path);
}
}
37 changes: 30 additions & 7 deletions vscode-web-ext/src/web/core/fileSystemWrapperBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,22 @@ import { FunctionsMap, ProxiesMap } from './FunctionsMap';

const ExcludedFolders = ['node_modules', 'target', 'bin', 'obj', '.vs', '.vscode', '.env', '.python_packages', '.git', '.github'];

export type RegExAndPos = { regex: RegExp, pos: number };

// Base class for implementing filesystem wrappers
export abstract class FileSystemWrapperBase {

public abstract dirName(path1: string): string;

public abstract joinPath(path1: string, path2: string): string;

protected abstract readFile(path: string): Promise<string>;
public abstract readFile(path: string): Promise<string>;

protected abstract isDirectory(path: string): Promise<boolean>;
public abstract isDirectory(path: string): Promise<boolean>;

protected abstract readDir(path: string): Promise<string[]>;
public abstract readDir(path: string): Promise<string[]>;

protected abstract pathExists(path: string): Promise<boolean>;
public abstract pathExists(path: string): Promise<boolean>;

async readFunctionsJson(hostJsonFolder: string, log: (s: any) => void): Promise<FunctionsMap> {

Expand Down Expand Up @@ -125,6 +127,27 @@ export abstract class FileSystemWrapperBase {
return !!javaFileMatch;
}

async isPowershellProjectAsync(projectFolder: string): Promise<boolean> {

const firstFunctionJsonFile = await this.findFileRecursivelyAsync(projectFolder, `function.json`, false);

if (!firstFunctionJsonFile || !firstFunctionJsonFile.filePath) {
return false;
}

const psFileMatch = await this.findFileRecursivelyAsync(this.dirName(firstFunctionJsonFile.filePath), `.+\\.ps1$`, false);

return !!psFileMatch;
}

async isPythonV2ProjectAsync(projectFolder: string): Promise<boolean> {

const pyFileMatch = await this.findFileRecursivelyAsync(projectFolder, `.+\\.py$`, false);
const functionJsonFileMatch = await this.findFileRecursivelyAsync(projectFolder, `function.json`, false);

return !!pyFileMatch && !functionJsonFileMatch;
}

async findFileRecursivelyAsync(folder: string, fileName: string | RegExp, returnFileContents: boolean, pattern?: RegExp)
: Promise<{ filePath: string, code?: string, pos?: number, length?: number } | undefined> {

Expand Down Expand Up @@ -204,16 +227,16 @@ export abstract class FileSystemWrapperBase {
}
}

async * findFunctionsRecursivelyAsync(folder: string, fileNameRegex: RegExp, functionAttributeRegex: RegExp, functionNamePosInRegex: number): AsyncGenerator<any> {
async * findFunctionsRecursivelyAsync(folder: string, fileNameRegex: RegExp, functionAttributeRegex: RegExAndPos): AsyncGenerator<any> {

for await (const fullPath of this.findFilesRecursivelyAsync(folder, fileNameRegex)) {

const code = await this.readFile(fullPath);

var match: RegExpExecArray | null;
while (!!(match = functionAttributeRegex.exec(code))) {
while (!!(match = functionAttributeRegex.regex.exec(code))) {

let functionName = cleanupFunctionName(match[functionNamePosInRegex]);
let functionName = cleanupFunctionName(match[functionAttributeRegex.pos]);

const functionAttributeEndPos = match.index + match[0].length;

Expand Down
32 changes: 32 additions & 0 deletions vscode-web-ext/src/web/core/functionProjectCodeParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { FunctionsMap } from "./FunctionsMap";

import { FunctionProjectParserBase } from './functionProjectParserBase';
import { RegExAndPos } from "./fileSystemWrapperBase";

export abstract class FunctionProjectCodeParser extends FunctionProjectParserBase {

public async traverseFunctions(projectFolder: string): Promise<FunctionsMap>{

let functions: FunctionsMap;

functions = await this.traverseProjectCode(projectFolder);

// Now enriching it with more info extracted from code
functions = await this.mapOrchestratorsAndActivitiesAsync(functions, projectFolder);

return functions;
}

protected abstract traverseProjectCode(projectFolder: string): Promise<FunctionsMap>;

protected getFunctionStartRegex(funcName: string): RegExp {
return new RegExp(`FunctionName(Attribute)?\\s*\\(\\s*(nameof\\s*\\(\\s*|["'\`]|[\\w\\s\\.]+\\.\\s*)${funcName}\\s*["'\`\\)]{1}`)
}

protected getFunctionAttributeRegex(): RegExAndPos {
return {
regex: new RegExp(`\\[\\s*Function(Name)?(Attribute)?\\s*\\((["\\w\\s\\.\\(\\)-]+)\\)\\s*\\]`, 'g'),
pos: 3
};
}
}

0 comments on commit 9a4bf8b

Please sign in to comment.