Skip to content

Commit e869c8d

Browse files
committed
Refactor backend discovery AST parsing
1 parent c692b4f commit e869c8d

12 files changed

Lines changed: 92 additions & 28 deletions

File tree

packages/plugins/apps/src/backend/discovery.ts renamed to packages/plugins/apps/src/backend/ast-parsing/extract-backend-functions.ts

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,8 @@
55
import type { Declaration, Expression, Program } from 'estree';
66
import type { AstNode } from 'rollup';
77

8-
export interface BackendFunction {
9-
/** Relative path from project root to the .backend.ts file (without extension) */
10-
relativePath: string;
11-
/** Exported function name */
12-
name: string;
13-
/** Absolute path to the .backend.ts source file */
14-
absolutePath: string;
15-
/** Connection IDs this backend function is allowed to use. */
16-
allowedConnectionIds: string[];
17-
}
8+
import { isProgramNode } from './type-guards';
9+
import type { BackendExport } from './types';
1810

1911
/**
2012
* Extract exported value (non-type) symbols from an ESTree AST.
@@ -27,11 +19,11 @@ export interface BackendFunction {
2719
* @param ast - AstNode from `this.parse()` in unplugin's transform hook
2820
* @param filePath - Path to the source file (used in error messages)
2921
*/
30-
function isProgramNode(node: AstNode): node is AstNode & Program {
31-
return node.type === 'Program';
22+
export function extractExportedFunctions(ast: AstNode, filePath: string): string[] {
23+
return enumerateBackendExports(ast, filePath).map((backendExport) => backendExport.name);
3224
}
3325

34-
export function extractExportedFunctions(ast: AstNode, filePath: string): string[] {
26+
export function enumerateBackendExports(ast: AstNode, filePath: string): BackendExport[] {
3527
if (!isProgramNode(ast)) {
3628
throw new Error(
3729
`Expected a Program node from this.parse() for ${filePath}, got ${ast.type}`,
@@ -41,7 +33,7 @@ export function extractExportedFunctions(ast: AstNode, filePath: string): string
4133
// Build a map of top-level declarations so we can validate export specifiers.
4234
const declarations = buildDeclarationMap(ast);
4335

44-
const names: string[] = [];
36+
const backendExports: BackendExport[] = [];
4537
for (const node of ast.body) {
4638
// handles: export default ...
4739
if (node.type === 'ExportDefaultDeclaration') {
@@ -61,9 +53,16 @@ export function extractExportedFunctions(ast: AstNode, filePath: string): string
6153

6254
// handles: export function add() {} / export const add = ...
6355
if (node.declaration) {
64-
names.push(...namesFromDeclaration(node.declaration, filePath));
56+
backendExports.push(
57+
...namesFromDeclaration(node.declaration, filePath).map((name) => ({
58+
kind: 'local' as const,
59+
name,
60+
localName: name,
61+
})),
62+
);
6563
}
6664

65+
const source = typeof node.source?.value === 'string' ? node.source.value : null;
6766
for (const spec of node.specifiers) {
6867
if (spec.exported.type !== 'Identifier') {
6968
continue;
@@ -77,14 +76,37 @@ export function extractExportedFunctions(ast: AstNode, filePath: string): string
7776
// Validate specifier binding is callable when we can resolve it.
7877
// e.g. `const VERSION = '1.0'; export { VERSION };` — rejected
7978
// e.g. `function add() {}; export { add };` — allowed
79+
const localName =
80+
spec.local.type === 'Identifier'
81+
? spec.local.name
82+
: typeof spec.local.value === 'string'
83+
? spec.local.value
84+
: null;
85+
if (!localName) {
86+
continue;
87+
}
88+
89+
if (source) {
90+
backendExports.push({
91+
kind: 're-export',
92+
name: spec.exported.name,
93+
localName,
94+
source,
95+
});
96+
continue;
97+
}
98+
8099
if (spec.local.type === 'Identifier') {
81-
validateSpecifierBinding(spec.local.name, declarations, filePath);
100+
validateSpecifierBinding(localName, declarations, filePath);
82101
}
83-
// handles: export { add, multiply }
84-
names.push(spec.exported.name);
102+
backendExports.push({
103+
kind: 'local',
104+
name: spec.exported.name,
105+
localName,
106+
});
85107
}
86108
}
87-
return names;
109+
return backendExports;
88110
}
89111

90112
/** Init types that are definitively non-callable at runtime. */
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License.
2+
// This product includes software developed at Datadog (https://www.datadoghq.com/).
3+
// Copyright 2019-Present Datadog, Inc.
4+
5+
import type { Program } from 'estree';
6+
import type { AstNode } from 'rollup';
7+
8+
export function isProgramNode(node: AstNode): node is AstNode & Program {
9+
return node.type === 'Program';
10+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License.
2+
// This product includes software developed at Datadog (https://www.datadoghq.com/).
3+
// Copyright 2019-Present Datadog, Inc.
4+
5+
export type BackendExport = BackendLocalExport | BackendReExport;
6+
7+
export interface BackendLocalExport {
8+
kind: 'local';
9+
name: string;
10+
localName: string;
11+
}
12+
13+
export interface BackendReExport {
14+
kind: 're-export';
15+
name: string;
16+
localName: string;
17+
source: string;
18+
}

packages/plugins/apps/src/backend/discovery.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// This product includes software developed at Datadog (https://www.datadoghq.com/).
33
// Copyright 2019-Present Datadog, Inc.
44

5-
import { extractExportedFunctions } from '@dd/apps-plugin/backend/discovery';
5+
import { extractExportedFunctions } from '@dd/apps-plugin/backend/ast-parsing/extract-backend-functions';
66
import type { Program } from 'estree';
77
import type { AstNode } from 'rollup';
88

packages/plugins/apps/src/backend/encodeQueryName.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import { createHash } from 'crypto';
66
import path from 'path';
77

8-
import type { BackendFunction } from './discovery';
8+
import type { BackendFunction } from './types';
99

1010
/**
1111
* Encode a BackendFunction into an opaque query name string.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License.
2+
// This product includes software developed at Datadog (https://www.datadoghq.com/).
3+
// Copyright 2019-Present Datadog, Inc.
4+
5+
export interface BackendFunction {
6+
/** Relative path from project root to the .backend.ts file (without extension) */
7+
relativePath: string;
8+
/** Exported function name */
9+
name: string;
10+
/** Absolute path to the .backend.ts source file */
11+
absolutePath: string;
12+
/** Connection IDs this backend function is allowed to use. */
13+
allowedConnectionIds: string[];
14+
}

packages/plugins/apps/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ import path from 'path';
1313
import { createArchive } from './archive';
1414
import type { Asset } from './assets';
1515
import { collectAssets } from './assets';
16-
import type { BackendFunction } from './backend/discovery';
17-
import { extractExportedFunctions } from './backend/discovery';
16+
import { extractExportedFunctions } from './backend/ast-parsing/extract-backend-functions';
1817
import { encodeQueryName } from './backend/encodeQueryName';
1918
import { generateProxyModule } from './backend/proxy-codegen';
19+
import type { BackendFunction } from './backend/types';
2020
import { BACKEND_FILE_RE, CONFIG_KEY, PLUGIN_NAME } from './constants';
2121
import { resolveIdentifier } from './identifier';
2222
import type { AppsManifest, AppsOptions } from './types';

packages/plugins/apps/src/vite/build-backend-functions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import { tmpdir } from 'os';
88
import path from 'path';
99
import type { build } from 'vite';
1010

11-
import type { BackendFunction } from '../backend/discovery';
1211
import { encodeQueryName } from '../backend/encodeQueryName';
12+
import type { BackendFunction } from '../backend/types';
1313
import { generateVirtualEntryContent } from '../backend/virtual-entry';
1414

1515
import { getBaseBackendBuildConfig } from './build-config';

packages/plugins/apps/src/vite/dev-server.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import { EventEmitter } from 'events';
88
import type { IncomingMessage, ServerResponse } from 'http';
99
import nock from 'nock';
1010

11-
import type { BackendFunction } from '../backend/discovery';
1211
import { encodeQueryName } from '../backend/encodeQueryName';
12+
import type { BackendFunction } from '../backend/types';
1313

1414
const mockViteBuild = jest.fn();
1515

packages/plugins/apps/src/vite/dev-server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ import { randomUUID } from 'crypto';
1010
import type { IncomingMessage, ServerResponse } from 'http';
1111
import type { build } from 'vite';
1212

13-
import type { BackendFunction } from '../backend/discovery';
1413
import { encodeQueryName } from '../backend/encodeQueryName';
1514
import type { ExecuteActionRequest, ExecuteActionResponse } from '../backend/protocol';
15+
import type { BackendFunction } from '../backend/types';
1616
import { generateDevVirtualEntryContent } from '../backend/virtual-entry';
1717

1818
import { getBaseBackendBuildConfig } from './build-config';

0 commit comments

Comments
 (0)