@mondaydotcomorg/z2h-cli 0.29.5 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/dist/backend/handlers.d.ts +3 -0
  2. package/dist/backend/handlers.d.ts.map +1 -1
  3. package/dist/backend/handlers.js +7 -4
  4. package/dist/backend/parser/__tests__/ast.test.d.ts.map +1 -0
  5. package/dist/backend/parser/__tests__/parse.test.d.ts.map +1 -0
  6. package/dist/backend/parser/api-signatures.d.ts +4 -0
  7. package/dist/backend/parser/api-signatures.d.ts.map +1 -0
  8. package/dist/backend/parser/api-signatures.js +31 -0
  9. package/dist/backend/parser/api-usage.d.ts +5 -0
  10. package/dist/backend/parser/api-usage.d.ts.map +1 -0
  11. package/dist/backend/parser/api-usage.js +61 -0
  12. package/dist/backend/parser/bindings.d.ts +5 -0
  13. package/dist/backend/parser/bindings.d.ts.map +1 -0
  14. package/dist/backend/parser/bindings.js +84 -0
  15. package/dist/backend/parser/handler-declaration.d.ts.map +1 -0
  16. package/dist/backend/parser/member-path.d.ts +3 -0
  17. package/dist/backend/parser/member-path.d.ts.map +1 -0
  18. package/dist/backend/parser/member-path.js +31 -0
  19. package/dist/backend/parser/messages.d.ts.map +1 -0
  20. package/dist/backend/parser/parse.d.ts.map +1 -0
  21. package/dist/backend/{parse.js → parser/parse.js} +9 -9
  22. package/dist/backend/parser/static-value.d.ts +6 -0
  23. package/dist/backend/parser/static-value.d.ts.map +1 -0
  24. package/dist/backend/parser/static-value.js +117 -0
  25. package/dist/backend/parser/types.d.ts +53 -0
  26. package/dist/backend/parser/types.d.ts.map +1 -0
  27. package/dist/backend/parser/types.js +1 -0
  28. package/dist/backend/parser/utils.d.ts +19 -0
  29. package/dist/backend/parser/utils.d.ts.map +1 -0
  30. package/dist/backend/parser/utils.js +97 -0
  31. package/dist/backend/validate.d.ts +3 -3
  32. package/dist/backend/validate.d.ts.map +1 -1
  33. package/dist/backend/validate.js +18 -18
  34. package/dist/commands/backend.d.ts +2 -13
  35. package/dist/commands/backend.d.ts.map +1 -1
  36. package/dist/commands/backend.js +30 -21
  37. package/dist/commands/create.d.ts.map +1 -1
  38. package/dist/commands/create.js +1 -0
  39. package/dist/commands/deploy.d.ts.map +1 -1
  40. package/dist/commands/deploy.js +21 -10
  41. package/dist/commands/generate.d.ts.map +1 -1
  42. package/dist/commands/generate.js +6 -0
  43. package/dist/esm/backend/handlers.d.ts +3 -0
  44. package/dist/esm/backend/handlers.d.ts.map +1 -1
  45. package/dist/esm/backend/handlers.mjs +7 -4
  46. package/dist/esm/backend/parser/__tests__/ast.test.d.ts.map +1 -0
  47. package/dist/esm/backend/parser/__tests__/parse.test.d.ts.map +1 -0
  48. package/dist/esm/backend/parser/api-signatures.d.ts +4 -0
  49. package/dist/esm/backend/parser/api-signatures.d.ts.map +1 -0
  50. package/dist/esm/backend/parser/api-signatures.mjs +24 -0
  51. package/dist/esm/backend/parser/api-usage.d.ts +5 -0
  52. package/dist/esm/backend/parser/api-usage.d.ts.map +1 -0
  53. package/dist/esm/backend/parser/api-usage.mjs +58 -0
  54. package/dist/esm/backend/parser/bindings.d.ts +5 -0
  55. package/dist/esm/backend/parser/bindings.d.ts.map +1 -0
  56. package/dist/esm/backend/parser/bindings.mjs +81 -0
  57. package/dist/esm/backend/parser/handler-declaration.d.ts.map +1 -0
  58. package/dist/esm/backend/parser/member-path.d.ts +3 -0
  59. package/dist/esm/backend/parser/member-path.d.ts.map +1 -0
  60. package/dist/esm/backend/parser/member-path.mjs +29 -0
  61. package/dist/esm/backend/parser/messages.d.ts.map +1 -0
  62. package/dist/esm/backend/parser/parse.d.ts.map +1 -0
  63. package/dist/esm/backend/{parse.mjs → parser/parse.mjs} +1 -1
  64. package/dist/esm/backend/parser/static-value.d.ts +6 -0
  65. package/dist/esm/backend/parser/static-value.d.ts.map +1 -0
  66. package/dist/esm/backend/parser/static-value.mjs +114 -0
  67. package/dist/esm/backend/parser/types.d.ts +53 -0
  68. package/dist/esm/backend/parser/types.d.ts.map +1 -0
  69. package/dist/esm/backend/parser/types.mjs +1 -0
  70. package/dist/esm/backend/parser/utils.d.ts +19 -0
  71. package/dist/esm/backend/parser/utils.d.ts.map +1 -0
  72. package/dist/esm/backend/parser/utils.mjs +88 -0
  73. package/dist/esm/backend/validate.d.ts +3 -3
  74. package/dist/esm/backend/validate.d.ts.map +1 -1
  75. package/dist/esm/backend/validate.mjs +8 -8
  76. package/dist/esm/commands/backend.d.ts +2 -13
  77. package/dist/esm/commands/backend.d.ts.map +1 -1
  78. package/dist/esm/commands/backend.mjs +31 -22
  79. package/dist/esm/commands/create.d.ts.map +1 -1
  80. package/dist/esm/commands/create.mjs +1 -0
  81. package/dist/esm/commands/deploy.d.ts.map +1 -1
  82. package/dist/esm/commands/deploy.mjs +23 -11
  83. package/dist/esm/commands/generate.d.ts.map +1 -1
  84. package/dist/esm/commands/generate.mjs +8 -2
  85. package/dist/esm/templates/app/usage.mjs +5 -0
  86. package/dist/esm/util/broker/app.d.ts +2 -0
  87. package/dist/esm/util/broker/app.d.ts.map +1 -1
  88. package/dist/esm/util/broker/app.mjs +4 -4
  89. package/dist/esm/util/machine-identity.d.ts +2 -3
  90. package/dist/esm/util/machine-identity.d.ts.map +1 -1
  91. package/dist/esm/util/machine-identity.mjs +7 -4
  92. package/dist/templates/app/usage.js +7 -0
  93. package/dist/util/broker/app.d.ts +2 -0
  94. package/dist/util/broker/app.d.ts.map +1 -1
  95. package/dist/util/broker/app.js +4 -4
  96. package/dist/util/machine-identity.d.ts +2 -3
  97. package/dist/util/machine-identity.d.ts.map +1 -1
  98. package/dist/util/machine-identity.js +11 -3
  99. package/handler-api/backend-runner/channels/snowflake-channel/run-query.ts +11 -14
  100. package/handler-api/signatures.json +26 -0
  101. package/package.json +1 -1
  102. package/src/backend/__tests__/handlers.test.ts +13 -2
  103. package/src/backend/__tests__/validate.test.ts +54 -0
  104. package/src/backend/handlers.ts +10 -5
  105. package/src/backend/parser/__tests__/ast.test.ts +252 -0
  106. package/src/backend/{__tests__ → parser/__tests__}/parse.test.ts +2 -2
  107. package/src/backend/parser/api-signatures.ts +23 -0
  108. package/src/backend/parser/api-usage.ts +73 -0
  109. package/src/backend/parser/bindings.ts +87 -0
  110. package/src/backend/parser/member-path.ts +31 -0
  111. package/src/backend/{parse.ts → parser/parse.ts} +1 -1
  112. package/src/backend/parser/static-value.ts +145 -0
  113. package/src/backend/parser/types.ts +66 -0
  114. package/src/backend/parser/utils.ts +106 -0
  115. package/src/backend/validate.ts +12 -8
  116. package/src/commands/__tests__/backend-deploy.test.ts +70 -5
  117. package/src/commands/__tests__/backend-validate.test.ts +11 -1
  118. package/src/commands/__tests__/create.test.ts +1 -0
  119. package/src/commands/__tests__/deploy.test.ts +28 -0
  120. package/src/commands/backend.ts +31 -20
  121. package/src/commands/create.ts +1 -0
  122. package/src/commands/deploy.ts +29 -12
  123. package/src/commands/generate.ts +8 -1
  124. package/src/templates/app/usage.json +1 -0
  125. package/src/util/broker/app.ts +17 -4
  126. package/src/util/machine-identity.ts +7 -3
  127. package/dist/backend/__tests__/ast.test.d.ts.map +0 -1
  128. package/dist/backend/__tests__/parse.test.d.ts.map +0 -1
  129. package/dist/backend/ast.d.ts +0 -39
  130. package/dist/backend/ast.d.ts.map +0 -1
  131. package/dist/backend/ast.js +0 -149
  132. package/dist/backend/handler-declaration.d.ts.map +0 -1
  133. package/dist/backend/messages.d.ts.map +0 -1
  134. package/dist/backend/parse.d.ts.map +0 -1
  135. package/dist/esm/backend/__tests__/ast.test.d.ts.map +0 -1
  136. package/dist/esm/backend/__tests__/parse.test.d.ts.map +0 -1
  137. package/dist/esm/backend/ast.d.ts +0 -39
  138. package/dist/esm/backend/ast.d.ts.map +0 -1
  139. package/dist/esm/backend/ast.mjs +0 -144
  140. package/dist/esm/backend/handler-declaration.d.ts.map +0 -1
  141. package/dist/esm/backend/messages.d.ts.map +0 -1
  142. package/dist/esm/backend/parse.d.ts.map +0 -1
  143. package/src/backend/__tests__/ast.test.ts +0 -53
  144. package/src/backend/ast.ts +0 -201
  145. /package/dist/backend/{__tests__ → parser/__tests__}/ast.test.d.ts +0 -0
  146. /package/dist/backend/{__tests__ → parser/__tests__}/parse.test.d.ts +0 -0
  147. /package/dist/backend/{handler-declaration.d.ts → parser/handler-declaration.d.ts} +0 -0
  148. /package/dist/backend/{handler-declaration.js → parser/handler-declaration.js} +0 -0
  149. /package/dist/backend/{messages.d.ts → parser/messages.d.ts} +0 -0
  150. /package/dist/backend/{messages.js → parser/messages.js} +0 -0
  151. /package/dist/backend/{parse.d.ts → parser/parse.d.ts} +0 -0
  152. /package/dist/esm/backend/{__tests__ → parser/__tests__}/ast.test.d.ts +0 -0
  153. /package/dist/esm/backend/{__tests__ → parser/__tests__}/parse.test.d.ts +0 -0
  154. /package/dist/esm/backend/{handler-declaration.d.ts → parser/handler-declaration.d.ts} +0 -0
  155. /package/dist/esm/backend/{handler-declaration.mjs → parser/handler-declaration.mjs} +0 -0
  156. /package/dist/esm/backend/{messages.d.ts → parser/messages.d.ts} +0 -0
  157. /package/dist/esm/backend/{messages.mjs → parser/messages.mjs} +0 -0
  158. /package/dist/esm/backend/{parse.d.ts → parser/parse.d.ts} +0 -0
  159. /package/src/backend/{handler-declaration.ts → parser/handler-declaration.ts} +0 -0
  160. /package/src/backend/{messages.ts → parser/messages.ts} +0 -0
@@ -1,144 +0,0 @@
1
- import { full, simple } from 'acorn-walk';
2
-
3
- /**
4
- * Every identifier a binding pattern introduces, with its property path from the
5
- * pattern's value. `path` is deliberately not defaulted: callers pass `[]` for
6
- * the root and `undefined` once the pattern stops being a plain property read.
7
- */
8
- function patternBindings(pattern, path) {
9
- switch (pattern.type) {
10
- case 'Identifier':
11
- return [{ name: pattern.name, path }];
12
- case 'ObjectPattern':
13
- return pattern.properties.flatMap(p => {
14
- if (p.type === 'RestElement') {
15
- return patternBindings(p.argument, undefined);
16
- }
17
- const key = propertyKey(p.key, p.computed);
18
- return patternBindings(p.value, path && key !== undefined ? [...path, key] : undefined);
19
- });
20
- case 'ArrayPattern':
21
- return pattern.elements.flatMap(e => (e ? patternBindings(e, undefined) : []));
22
- case 'AssignmentPattern':
23
- return patternBindings(pattern.left, undefined);
24
- case 'RestElement':
25
- return patternBindings(pattern.argument, undefined);
26
- default:
27
- // MemberExpression targets (`obj.x = …`) bind no names.
28
- return [];
29
- }
30
- }
31
- function propertyKey(key, computed) {
32
- if (key.type === 'Literal' && typeof key.value === 'string') {
33
- return key.value;
34
- }
35
- if (!computed && key.type === 'Identifier') {
36
- return key.name;
37
- }
38
- return undefined;
39
- }
40
- /** Every identifier a binding pattern introduces (`{ a, b: [c] } = …` → a, c). */
41
- const patternNames = (pattern) => patternBindings(pattern, []).map(b => b.name);
42
- function collectBindings(program) {
43
- const declarations = new Map();
44
- const unpinnable = new Set();
45
- const markUnpinnable = (pattern) => patternNames(pattern).forEach(n => unpinnable.add(n));
46
- full(program, node => {
47
- switch (node.type) {
48
- case 'VariableDeclaration':
49
- for (const declarator of node.declarations) {
50
- for (const { name, path } of patternBindings(declarator.id, [])) {
51
- const list = declarations.get(name) ?? [];
52
- list.push({ kind: node.kind, init: declarator.init, path });
53
- declarations.set(name, list);
54
- }
55
- }
56
- break;
57
- case 'AssignmentExpression':
58
- markUnpinnable(node.left);
59
- break;
60
- case 'UpdateExpression':
61
- if (node.argument.type === 'Identifier') {
62
- unpinnable.add(node.argument.name);
63
- }
64
- break;
65
- case 'ForInStatement':
66
- case 'ForOfStatement':
67
- if (node.left.type !== 'VariableDeclaration') {
68
- markUnpinnable(node.left);
69
- }
70
- break;
71
- case 'FunctionDeclaration':
72
- case 'FunctionExpression':
73
- case 'ArrowFunctionExpression':
74
- node.params.forEach(markUnpinnable);
75
- break;
76
- case 'CatchClause':
77
- if (node.param) {
78
- markUnpinnable(node.param);
79
- }
80
- break;
81
- }
82
- });
83
- return { declarations, unpinnable };
84
- }
85
- /** `a.b.c` / `a['b'].c` → root `a`, parts `[b, c]`; `undefined` if any link is dynamic or the root is not an identifier. */
86
- function memberChain(node) {
87
- if (node.type === 'ChainExpression') {
88
- return memberChain(node.expression);
89
- }
90
- const parts = [];
91
- let cur = node;
92
- while (cur.type === 'MemberExpression') {
93
- const key = propertyKey(cur.property, cur.computed);
94
- if (key === undefined) {
95
- return undefined;
96
- }
97
- parts.unshift(key);
98
- cur = cur.object;
99
- }
100
- return cur.type === 'Identifier' ? { root: cur.name, parts } : undefined;
101
- }
102
- function createPathResolver(bindings, contextParam) {
103
- function resolveChain(node, resolving) {
104
- const chain = memberChain(node);
105
- if (!chain) {
106
- return undefined;
107
- }
108
- return [resolveRoot(chain.root, resolving), ...chain.parts].join('.');
109
- }
110
- function resolveRoot(name, resolving) {
111
- if (contextParam !== undefined && name === contextParam) {
112
- return 'ctx';
113
- }
114
- if (bindings.unpinnable.has(name) || resolving.has(name)) {
115
- return name;
116
- }
117
- const decls = bindings.declarations.get(name);
118
- if (!decls || decls.length !== 1) {
119
- return name;
120
- }
121
- const [{ kind, init, path }] = decls;
122
- if (kind !== 'const' || !init || !path) {
123
- return name;
124
- }
125
- const base = resolveChain(init, new Set(resolving).add(name));
126
- return base ? [base, ...path].join('.') : name;
127
- }
128
- return { pathOf: node => resolveChain(node, new Set()) };
129
- }
130
- /** Every `ctx.api.v1.<domain>` the program touches (known or not), through `paths`. */
131
- function collectApiDomains(program, paths) {
132
- const apiDomains = new Set();
133
- simple(program, {
134
- MemberExpression(node) {
135
- const [obj, api, version, domain] = paths.pathOf(node)?.split('.') ?? [];
136
- if (obj === 'ctx' && api === 'api' && version === 'v1' && domain) {
137
- apiDomains.add(domain);
138
- }
139
- },
140
- });
141
- return apiDomains;
142
- }
143
-
144
- export { collectApiDomains, collectBindings, createPathResolver, patternNames };
@@ -1 +0,0 @@
1
- {"version":3,"file":"handler-declaration.d.ts","sourceRoot":"","sources":["../../../src/backend/handler-declaration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,UAAU,EACV,mBAAmB,EAEnB,iBAAiB,EAEjB,SAAS,EACV,MAAM,OAAO,CAAC;AAMf,MAAM,MAAM,kBAAkB,GAC1B;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,mBAAmB,CAAC,QAAQ,CAAC,CAAA;CAAE,GAC3D;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,UAAU,GAAG,IAAI,GAAG,SAAS,CAAA;CAAE,CAAC;AAE9D,8DAA8D;AAC9D,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,SAAS,CAAC,SAAS,GAAG,iBAAiB,CAAC,EAAE,GAC/C,kBAAkB,GAAG,SAAS,CAahC;AAED,yGAAyG;AACzG,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,GAAG,iBAAiB,GAAG,OAAO,CAe3E;AAsCD,mGAAmG;AACnG,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,kBAAkB,GAAG,MAAM,GAAG,SAAS,CAG3F;AAED,gGAAgG;AAChG,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAK/C,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../../src/backend/messages.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,QAAQ;uBACA,MAAM;yBACJ,MAAM;0BACL,MAAM;iCAEC,MAAM;0BAEb,MAAM;6BAEH,MAAM,UAAU,MAAM;wBAE3B,MAAM,QAAQ,MAAM,UAAU,MAAM,UAAU,MAAM;CAEzE,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../../src/backend/parse.ts"],"names":[],"mappings":"AAAA,OAAO,EAA8D,KAAK,OAAO,EAAkB,MAAM,OAAO,CAAC;AAkDjH;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAuBhE"}
@@ -1,53 +0,0 @@
1
- import { parse, type ExpressionStatement, type Program } from 'acorn';
2
- import { describe, expect, it } from 'vitest';
3
- import { collectBindings, createPathResolver } from '../ast';
4
-
5
- const program = (source: string): Program => parse(source, { ecmaVersion: 'latest', sourceType: 'script' });
6
-
7
- /** Resolves the expression in the program's last statement. */
8
- const resolveLast = (source: string, contextParam?: string) => {
9
- const p = program(source);
10
- const last = p.body[p.body.length - 1] as ExpressionStatement;
11
- return createPathResolver(collectBindings(p), contextParam).pathOf(last.expression);
12
- };
13
-
14
- describe('createPathResolver', () => {
15
- it('returns the literal chain when nothing is aliased', () => {
16
- expect(resolveLast('ctx.api.v1.db.get;')).toBe('ctx.api.v1.db.get');
17
- });
18
-
19
- it('treats computed string literals as static keys', () => {
20
- expect(resolveLast('ctx.api.v1["db"].get;')).toBe('ctx.api.v1.db.get');
21
- expect(resolveLast("const { ['db']: store } = ctx.api.v1; store.get;")).toBe('ctx.api.v1.db.get');
22
- });
23
-
24
- it('returns undefined for dynamic links and non-identifier roots', () => {
25
- expect(resolveLast('ctx.api.v1[db].get;')).toBeUndefined();
26
- expect(resolveLast('fn().db;')).toBeUndefined();
27
- expect(resolveLast('"x";')).toBeUndefined();
28
- });
29
-
30
- it('rewrites the handler context parameter to ctx', () => {
31
- expect(resolveLast('context.api.v1.db.get;', 'context')).toBe('ctx.api.v1.db.get');
32
- expect(resolveLast('const sf = context.api.v1.snowflake; sf.query;', 'context')).toBe('ctx.api.v1.snowflake.query');
33
- });
34
-
35
- it('follows const aliases, including destructuring and chains', () => {
36
- expect(resolveLast('const sf = ctx.api.v1.snowflake; sf.query;')).toBe('ctx.api.v1.snowflake.query');
37
- expect(resolveLast('const { v1 } = ctx.api; v1.db;')).toBe('ctx.api.v1.db');
38
- expect(resolveLast('const { v1: { db: store } } = ctx.api; store.get;')).toBe('ctx.api.v1.db.get');
39
- expect(resolveLast('const a = ctx.api; const b = a.v1; const c = b.db; c;')).toBe('ctx.api.v1.db');
40
- });
41
-
42
- it('leaves unpinnable names as written', () => {
43
- expect(resolveLast('let sf = ctx.api.v1.snowflake; sf.query;')).toBe('sf.query');
44
- expect(resolveLast('const sf = ctx.api.v1.snowflake; sf = 1; sf.query;')).toBe('sf.query');
45
- expect(resolveLast('const sf = ctx.api.v1.snowflake; function f(sf) {} sf.query;')).toBe('sf.query');
46
- expect(resolveLast('const sf = ctx.api.v1.snowflake; { const sf = 1; } sf.query;')).toBe('sf.query');
47
- expect(resolveLast('const [sf] = ctx.api; sf.query;')).toBe('sf.query');
48
- });
49
-
50
- it('does not loop on circular aliases', () => {
51
- expect(resolveLast('const a = b.x; const b = a.y; a.z;')).toBe('a.y.x.z');
52
- });
53
- });
@@ -1,201 +0,0 @@
1
- import type { AnyNode, Expression, Pattern, Program, VariableDeclaration } from 'acorn';
2
- import { full as walkFull, simple as walkSimple } from 'acorn-walk';
3
-
4
- // ─────────── Bindings ───────────
5
-
6
- export interface Declaration {
7
- kind: VariableDeclaration['kind'];
8
- init: Expression | null | undefined;
9
- /**
10
- * Property path from `init` to this binding: `[]` for `const x = init`,
11
- * `['a', 'b']` for `const { a: { b: x } } = init`, `undefined` when the value
12
- * is not a plain property read (array/rest/default patterns).
13
- */
14
- path: string[] | undefined;
15
- }
16
-
17
- export interface Bindings {
18
- /** Every `var|let|const <name> = …` in the file, by name. */
19
- declarations: Map<string, Declaration[]>;
20
- /** Names whose value cannot be pinned: reassigned, updated, or bound as parameters. */
21
- unpinnable: Set<string>;
22
- }
23
-
24
- interface PatternBinding {
25
- name: string;
26
- path: string[] | undefined;
27
- }
28
-
29
- /**
30
- * Every identifier a binding pattern introduces, with its property path from the
31
- * pattern's value. `path` is deliberately not defaulted: callers pass `[]` for
32
- * the root and `undefined` once the pattern stops being a plain property read.
33
- */
34
- function patternBindings(pattern: Pattern, path: string[] | undefined): PatternBinding[] {
35
- switch (pattern.type) {
36
- case 'Identifier':
37
- return [{ name: pattern.name, path }];
38
- case 'ObjectPattern':
39
- return pattern.properties.flatMap(p => {
40
- if (p.type === 'RestElement') {
41
- return patternBindings(p.argument, undefined);
42
- }
43
- const key = propertyKey(p.key, p.computed);
44
- return patternBindings(p.value, path && key !== undefined ? [...path, key] : undefined);
45
- });
46
- case 'ArrayPattern':
47
- return pattern.elements.flatMap(e => (e ? patternBindings(e, undefined) : []));
48
- case 'AssignmentPattern':
49
- return patternBindings(pattern.left, undefined);
50
- case 'RestElement':
51
- return patternBindings(pattern.argument, undefined);
52
- default:
53
- // MemberExpression targets (`obj.x = …`) bind no names.
54
- return [];
55
- }
56
- }
57
-
58
- function propertyKey(key: AnyNode, computed: boolean): string | undefined {
59
- if (key.type === 'Literal' && typeof key.value === 'string') {
60
- return key.value;
61
- }
62
- if (!computed && key.type === 'Identifier') {
63
- return key.name;
64
- }
65
- return undefined;
66
- }
67
-
68
- /** Every identifier a binding pattern introduces (`{ a, b: [c] } = …` → a, c). */
69
- export const patternNames = (pattern: Pattern): string[] => patternBindings(pattern, []).map(b => b.name);
70
-
71
- export function collectBindings(program: Program): Bindings {
72
- const declarations = new Map<string, Declaration[]>();
73
- const unpinnable = new Set<string>();
74
- const markUnpinnable = (pattern: Pattern) => patternNames(pattern).forEach(n => unpinnable.add(n));
75
-
76
- walkFull(program, node => {
77
- switch (node.type) {
78
- case 'VariableDeclaration':
79
- for (const declarator of node.declarations) {
80
- for (const { name, path } of patternBindings(declarator.id, [])) {
81
- const list = declarations.get(name) ?? [];
82
- list.push({ kind: node.kind, init: declarator.init, path });
83
- declarations.set(name, list);
84
- }
85
- }
86
- break;
87
- case 'AssignmentExpression':
88
- markUnpinnable(node.left);
89
- break;
90
- case 'UpdateExpression':
91
- if (node.argument.type === 'Identifier') {
92
- unpinnable.add(node.argument.name);
93
- }
94
- break;
95
- case 'ForInStatement':
96
- case 'ForOfStatement':
97
- if (node.left.type !== 'VariableDeclaration') {
98
- markUnpinnable(node.left);
99
- }
100
- break;
101
- case 'FunctionDeclaration':
102
- case 'FunctionExpression':
103
- case 'ArrowFunctionExpression':
104
- node.params.forEach(markUnpinnable);
105
- break;
106
- case 'CatchClause':
107
- if (node.param) {
108
- markUnpinnable(node.param);
109
- }
110
- break;
111
- }
112
- });
113
-
114
- return { declarations, unpinnable };
115
- }
116
-
117
- // ─────────── Member paths ───────────
118
-
119
- interface MemberChain {
120
- root: string;
121
- parts: string[];
122
- }
123
-
124
- /** `a.b.c` / `a['b'].c` → root `a`, parts `[b, c]`; `undefined` if any link is dynamic or the root is not an identifier. */
125
- function memberChain(node: AnyNode): MemberChain | undefined {
126
- if (node.type === 'ChainExpression') {
127
- return memberChain(node.expression);
128
- }
129
- const parts: string[] = [];
130
- let cur: AnyNode = node;
131
- while (cur.type === 'MemberExpression') {
132
- const key = propertyKey(cur.property, cur.computed);
133
- if (key === undefined) {
134
- return undefined;
135
- }
136
- parts.unshift(key);
137
- cur = cur.object;
138
- }
139
- return cur.type === 'Identifier' ? { root: cur.name, parts } : undefined;
140
- }
141
-
142
- export interface PathResolver {
143
- /**
144
- * Dotted path of a member chain, following `const` aliases back to their
145
- * source: with `const sf = ctx.api.v1.snowflake`, `sf.query` resolves to
146
- * `ctx.api.v1.snowflake.query`. Destructuring counts too
147
- * (`const { snowflake: sf } = ctx.api.v1`). `undefined` if any link is dynamic.
148
- *
149
- * Only single, never-reassigned, never-shadowed `const` bindings are followed —
150
- * anything else is left as written, so a `let` alias or a parameter of the same
151
- * name is never mistaken for the channel. The handler's first parameter is
152
- * treated as `ctx` even when renamed (`handler(context)` → `context.api.v1.db`
153
- * resolves as `ctx.api.v1.db`).
154
- */
155
- pathOf(node: AnyNode): string | undefined;
156
- }
157
-
158
- export function createPathResolver(bindings: Bindings, contextParam?: string): PathResolver {
159
- function resolveChain(node: AnyNode, resolving: ReadonlySet<string>): string | undefined {
160
- const chain = memberChain(node);
161
- if (!chain) {
162
- return undefined;
163
- }
164
- return [resolveRoot(chain.root, resolving), ...chain.parts].join('.');
165
- }
166
-
167
- function resolveRoot(name: string, resolving: ReadonlySet<string>): string {
168
- if (contextParam !== undefined && name === contextParam) {
169
- return 'ctx';
170
- }
171
- if (bindings.unpinnable.has(name) || resolving.has(name)) {
172
- return name;
173
- }
174
- const decls = bindings.declarations.get(name);
175
- if (!decls || decls.length !== 1) {
176
- return name;
177
- }
178
- const [{ kind, init, path }] = decls;
179
- if (kind !== 'const' || !init || !path) {
180
- return name;
181
- }
182
- const base = resolveChain(init, new Set(resolving).add(name));
183
- return base ? [base, ...path].join('.') : name;
184
- }
185
-
186
- return { pathOf: node => resolveChain(node, new Set()) };
187
- }
188
-
189
- /** Every `ctx.api.v1.<domain>` the program touches (known or not), through `paths`. */
190
- export function collectApiDomains(program: Program, paths: PathResolver): Set<string> {
191
- const apiDomains = new Set<string>();
192
- walkSimple(program, {
193
- MemberExpression(node) {
194
- const [obj, api, version, domain] = paths.pathOf(node)?.split('.') ?? [];
195
- if (obj === 'ctx' && api === 'api' && version === 'v1' && domain) {
196
- apiDomains.add(domain);
197
- }
198
- },
199
- });
200
- return apiDomains;
201
- }
File without changes
File without changes