Skip to content

Commit

Permalink
Update find-source-function tests after more fiddling with the test-s…
Browse files Browse the repository at this point in the history
…tatic-analysis codebase
  • Loading branch information
brettimus committed Oct 7, 2024
1 parent c22ca9b commit a08b9c9
Show file tree
Hide file tree
Showing 17 changed files with 16,046 additions and 54 deletions.
8 changes: 4 additions & 4 deletions api/src/lib/ai/openai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@ export async function generateRequestWithOpenAI({
`persona: ${persona}`,
`method: ${method}`,
`path: ${path}`,
// `handler: ${handler}`,
// `handlerContext: ${handlerContext}`,
`handler: ${handler}`,
`handlerContext: ${handlerContext}`,
// `openApiSpec: ${openApiSpec}`,
// `middleware: ${middleware}`,
// `middlewareContext: ${middlewareContext}`,
`middleware: ${middleware}`,
`middlewareContext: ${middlewareContext}`,
);
const openaiClient = new OpenAI({ apiKey, baseURL: baseUrl });
const userPrompt = await invokeRequestGenerationPrompt({
Expand Down
5 changes: 2 additions & 3 deletions api/src/lib/expand-function/expand-function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import {
contextForImport,
extractPackageName,
isDependency,
resolveModulePath,
} from "./imports/index.js";
import { searchFunction } from "./search-function/index.js";
Expand Down Expand Up @@ -313,9 +314,7 @@ async function extractContext(

// HACK - If we resolved the definition to node_modules,
// we can skip any recursive expansion and just add the import as context for now
const isNodeModule = sourceDefinition?.uri?.includes("node_modules");

if (isNodeModule) {
if (isDependency(sourceDefinition?.uri)) {
if (debug) {
logger.debug(
`[debug] ${identifier.name} is likely an installed dependency`,
Expand Down
4 changes: 4 additions & 0 deletions api/src/lib/expand-function/imports/is-dependency.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
const dependencyFolders = ["node_modules", ".yarn", ".pnpm"];

export function isDependency(filePath: string): boolean {
if (!filePath) {
return false;
}

// Normalize the path to handle Windows
const normalizedPath = filePath.replace(/\\/g, "/");
return dependencyFolders.some((folder) =>
Expand Down
122 changes: 86 additions & 36 deletions api/src/lib/find-source-function/find-source-function.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,59 +6,92 @@ import { findSourceFunctions } from "./find-source-function.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// Resolve the path and analyze the source maps in our test-data directory
const testContentsDir = path.resolve(__dirname, "./test-data/hono-js-tracker");

// NOTE - This covers an edge case I found when analyzing code from hono-github-tracker,
// where the source code mapping to the original function would cut off the final character,
// and I think the cause of the issue was a trailing comma after the function's closing brace,
// since it was being passed as an argument to another function.
describe("findSourceFunction", () => {
describe("handler", () => {
it("should find the source handler function", async () => {
const { HANDLER_COMPILED_CODE, HANDLE_SOURCE_CODE } = getHandlerCode();
const jsFilePath = path.join(testContentsDir, "index.js");
const functionText = HANDLER_COMPILED_CODE;
describe("test-static-analysis", () => {
// Resolve the path and analyze the source maps in our test-data directory
const testStaticAnalysisTestDir = path.resolve(
__dirname,
"./test-data/test-static-analysis",
);

it("should find non-async arrow function", async () => {
const {
NON_ASYNC_ARROW_FUNCTION_COMPILED_CODE,
NON_ASYNC_ARROW_FUNCTION_SOURCE_CODE,
} = getStaticAnalysisTestNonAsyncArrowFunctionCode();
const jsFilePath = path.join(testStaticAnalysisTestDir, "index.js");
const functionText = NON_ASYNC_ARROW_FUNCTION_COMPILED_CODE;
const result = await findSourceFunctions(jsFilePath, functionText);

expect(result).toHaveLength(1);
expect(result[0]?.sourceFunction).toEqual(HANDLE_SOURCE_CODE);
expect(result[0]?.sourceFunction).toEqual(
NON_ASYNC_ARROW_FUNCTION_SOURCE_CODE,
);
});
});

describe("middleware", () => {
it("should find the source middleware function", async () => {
const { MIDDLEWARE_COMPILED_CODE, MIDDLEWARE_SOURCE_CODE } =
getMiddlewareCode();
const jsFilePath = path.join(testContentsDir, "index.js");
const functionText = MIDDLEWARE_COMPILED_CODE;
const result = await findSourceFunctions(jsFilePath, functionText);
describe("hono-github-tracker", () => {
// Resolve the path and analyze the source maps in our test-data directory
const honoGithubTrackerTestDir = path.resolve(
__dirname,
"./test-data/hono-js-tracker",
);

describe("handler", () => {
it("should find the source handler function", async () => {
const { HANDLER_COMPILED_CODE, HANDLE_SOURCE_CODE } =
getGithubTrackerHandlerCode();
const jsFilePath = path.join(honoGithubTrackerTestDir, "index.js");
const functionText = HANDLER_COMPILED_CODE;
const result = await findSourceFunctions(jsFilePath, functionText);

expect(result).toHaveLength(1);
expect(result[0]?.sourceFunction).toEqual(HANDLE_SOURCE_CODE);
});
});

expect(result).toHaveLength(1);
expect(result[0]?.sourceFunction).toEqual(MIDDLEWARE_SOURCE_CODE);
describe("middleware", () => {
it("should find the source middleware function", async () => {
const { MIDDLEWARE_COMPILED_CODE, MIDDLEWARE_SOURCE_CODE } =
getGithubTrackerMiddlewareCode();
const jsFilePath = path.join(honoGithubTrackerTestDir, "index.js");
const functionText = MIDDLEWARE_COMPILED_CODE;
const result = await findSourceFunctions(jsFilePath, functionText);

expect(result).toHaveLength(1);
expect(result[0]?.sourceFunction).toEqual(MIDDLEWARE_SOURCE_CODE);
});
});
});

describe("batch input", () => {
it("should be able to do multiple lookups", async () => {
const { HANDLER_COMPILED_CODE, HANDLE_SOURCE_CODE } = getHandlerCode();
const { MIDDLEWARE_COMPILED_CODE, MIDDLEWARE_SOURCE_CODE } =
getMiddlewareCode();
const jsFilePath = path.join(testContentsDir, "index.js");
const functionDefinitions = [
HANDLER_COMPILED_CODE,
MIDDLEWARE_COMPILED_CODE,
];
const result = await findSourceFunctions(jsFilePath, functionDefinitions);

expect(result).toHaveLength(2);
expect(result[0]?.sourceFunction).toEqual(HANDLE_SOURCE_CODE);
expect(result[1]?.sourceFunction).toEqual(MIDDLEWARE_SOURCE_CODE);
describe("batch input", () => {
it("should be able to do multiple lookups", async () => {
const { HANDLER_COMPILED_CODE, HANDLE_SOURCE_CODE } =
getGithubTrackerHandlerCode();
const { MIDDLEWARE_COMPILED_CODE, MIDDLEWARE_SOURCE_CODE } =
getGithubTrackerMiddlewareCode();
const jsFilePath = path.join(honoGithubTrackerTestDir, "index.js");
const functionDefinitions = [
HANDLER_COMPILED_CODE,
MIDDLEWARE_COMPILED_CODE,
];
const result = await findSourceFunctions(
jsFilePath,
functionDefinitions,
);

expect(result).toHaveLength(2);
expect(result[0]?.sourceFunction).toEqual(HANDLE_SOURCE_CODE);
expect(result[1]?.sourceFunction).toEqual(MIDDLEWARE_SOURCE_CODE);
});
});
});
});

function getHandlerCode() {
function getGithubTrackerHandlerCode() {
const HANDLER_COMPILED_CODE =
'async (c) => {\n const db2 = c.var.db;\n const webhooks2 = c.var.webhooks;\n const fetchUserById = c.var.fetchUserById;\n webhooks2.on(\n ["issues.opened", "star.created", "watch.started"],\n async ({ payload, name }) => {\n const userId = payload.sender.id;\n try {\n await db2.insert(repositories).values({\n description: payload.repository.description,\n fullName: payload.repository.full_name,\n id: payload.repository.id,\n name: payload.repository.name,\n stargazersCount: payload.repository.stargazers_count,\n watchersCount: payload.repository.watchers_count\n }).onConflictDoUpdate({\n target: repositories.id,\n set: {\n stargazersCount: payload.repository.stargazers_count,\n watchersCount: payload.repository.watchers_count\n }\n });\n } catch (error) {\n return c.text(`Error fetching repository: ${error}`, 500);\n }\n try {\n const user = await fetchUserById(userId);\n await db2.insert(users).values({\n avatar: user.avatar_url,\n company: user.company,\n emailAddress: user.email,\n handle: user.login,\n id: user.id,\n location: user.location,\n name: user.name,\n twitterHandle: user.twitter_username\n }).onConflictDoNothing({ target: users.id });\n } catch (error) {\n return c.text(`Error inserting user: ${error}`, 500);\n }\n let eventId;\n if (name === "issues") {\n eventId = payload.issue.id;\n }\n try {\n await db2.insert(events).values({\n eventId,\n eventAction: payload.action,\n eventName: name,\n repoId: payload.repository.id,\n userId\n });\n } catch (error) {\n return c.text(`Error inserting event: ${error}`, 500);\n }\n }\n );\n}';
const HANDLE_SOURCE_CODE = `async (c) => {
Expand Down Expand Up @@ -137,7 +170,7 @@ function getHandlerCode() {
return { HANDLER_COMPILED_CODE, HANDLE_SOURCE_CODE };
}

function getMiddlewareCode() {
function getGithubTrackerMiddlewareCode() {
const MIDDLEWARE_COMPILED_CODE =
'async (c, next) => {\n const secret = c.env.GITHUB_WEBHOOK_SECRET;\n const webhooks2 = getWebhooksInstance(secret);\n c.set("webhooks", webhooks2);\n await next();\n const id = c.req.header("x-github-delivery");\n const signature = c.req.header("x-hub-signature-256");\n const name = c.req.header("x-github-event");\n if (!(id && name && signature)) {\n return c.text("Invalid webhook request", 403);\n }\n const payload = await c.req.text();\n try {\n await webhooks2.verifyAndReceive({\n id,\n name,\n signature,\n payload\n });\n return c.text("Webhook received & verified", 201);\n } catch (error) {\n return c.text(`Failed to verify Github Webhook request: ${error}`, 400);\n }\n }';
const MIDDLEWARE_SOURCE_CODE = `async (c, next) => {
Expand Down Expand Up @@ -173,3 +206,20 @@ function getMiddlewareCode() {

return { MIDDLEWARE_COMPILED_CODE, MIDDLEWARE_SOURCE_CODE };
}

function getStaticAnalysisTestNonAsyncArrowFunctionCode() {
const NON_ASYNC_ARROW_FUNCTION_COMPILED_CODE = `(c) => {
const auth = c.req.header("Authorization");
if (auth && PASSPHRASES.includes(auth)) {
return c.text("Hello Hono!");
}
return c.text("Unauthorized", 401);
}`;
const NON_ASYNC_ARROW_FUNCTION_SOURCE_CODE =
NON_ASYNC_ARROW_FUNCTION_COMPILED_CODE;

return {
NON_ASYNC_ARROW_FUNCTION_COMPILED_CODE,
NON_ASYNC_ARROW_FUNCTION_SOURCE_CODE,
};
}
17 changes: 13 additions & 4 deletions api/src/lib/find-source-function/find-source-function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,14 @@ async function findFunctionsByDefinition(
funcSource.replace(/\s+/g, " ").trim() ===
functionMap[key].normalized
) {
logger.debug(
`Found ArrowFunctionExpression ${funcSource} at ${node.loc.start.line}:${node.loc.start.column}`,
);
functionMap[key].foundLocation = {
startLine: node.loc.start.line,
startColumn: node.loc.start.column + 1,
startColumn: node.loc.start.column,
endLine: node.loc.end.line,
endColumn: node.loc.end.column + 1,
endColumn: node.loc.end.column,
};
}
}
Expand Down Expand Up @@ -268,13 +271,17 @@ export async function findSourceFunctions(
}

const lines = sourceContent.split("\n").slice(startLine - 1, endLine);
for (const line of lines) {
logger.debug(`Line: ${line}`);
}
const sourceFunction = lines
.map((line, index) => {
if (index === 0 && startLine === endLine) {
return line.slice(startColumn ?? 0, endColumn ?? 0);
}
if (index === 0) {
return line.slice(startColumn ?? 0);
const sliceFrom = startColumn ?? 0;
return line.slice(sliceFrom);
}
if (index === endLine - startLine) {
// MEGA HACK - Add 1 to the end column only if it ends in a comma
Expand All @@ -283,7 +290,9 @@ export async function findSourceFunctions(
// trailing commas in functions that are passed as arguments to other functions.
// In that case, we need to add 1 to the end column to account for something that gets odd when receiving the location back.
const endsInComma = line.endsWith(",");
return line.slice(0, (endColumn ?? 0) + (endsInComma ? 1 : 0));
const sliceTo = (endColumn ?? 0) + (endsInComma ? 1 : 0);
logger.debug(`Slicing end line ${line} to ${sliceTo}`);
return line.slice(0, sliceTo);
}
return line;
})
Expand Down
Loading

0 comments on commit a08b9c9

Please sign in to comment.